[享学Feign] 六、原生Feign的解码器Decoder、ErrorDecoder
你现在没有决定的权利,但你有决定未来的权利 代码下载地址: https://github.com/f641385712/feign-learning
前言
上文 介绍了Feign的编码器Encoder,本篇继续了解它的解码器
Decoder
,以及错误解码器ErrorDecoder
。正文
编码器作用于Request,那么解码器作用于Response,用于解析Http请求的响应,提取有用信息数据。
解码器Decoder
将HTTP响应
feign.Response
解码为指定类型的 单一对象 。当然触发它也有前提:
- 响应码是2xx
- 方法返回值既不是void/null,也不是
feign.Response
类型public interface Decoder { // response:代表请求响应 // type:代表方法的返回值类型 // 它还有个特点:抛出了三种异常 // 但其实除了IOException,其它两种都是unchecked异常 Object decode(Response response, Type type) throws IOException, DecodeException, FeignException; }
使用时需要注意:因为接口代理实例是通过
Feign.newInstance(Feign.Target)
来生成的,它是支持泛型处理的如List<Foo>
,所以当你自定义解码器的时候也请支持泛型类型。异常情况时(请求抛出异常,或者状态码不是2xx等),会有如下处理方案:
- 解码器引发的异常将包装在
DecodeException
中,除非它们已经是FeignException
的子类- 如果发生了404,但是没有配置
Feign.Builder.decode404()
解码的话,最终也会交给此解码器处理的(否则就不会交给此解码器)。说明:其实关于解码器的如何执行,什么时候执行逻辑,在 上一篇 对
SynchronousMethodHandler
源码分析出已经很清晰了解码器接口比编码器来得“幸运”,它不止一个实现,有自己的继承树:
StreamDecoder
支持返回值是
java.util.stream.Stream
类型,但是它依赖于一个iteratorDecoder
来实现。iteratorDecoder
它负责实际的解码动作:Iterator<?> iterator = (Iterator) iteratorDecoder.decode(...)
,最终解码出来的必须是个Iterator
类型,比如List。下面给个示例:
Http Server端:
@GetMapping("/demo1/list") public List<String> getDemo1List() { return Arrays.asList("A", "B", "C"); }
Client端书写:
public interface DecoderClient { // 返回值为Stream类型 @RequestLine("GET /feign/demo1/list") Stream<String> getDemo1List(); }
这个时候需要
StreamDecoder
编码器的支持,并且还需要一个支持Iterator
的解码器:@Test public void fun1() { DecoderClient client = Feign.builder() .logger(new Logger.ErrorLogger()).logLevel(Logger.Level.FULL) .retryer(Retryer.NEVER_RETRY) // 自定义一个临时的iterator解码器来实现 .decoder(StreamDecoder.create((response, type) -> { // resultStr的值是:["A","B","C"] String resultStr = Util.toString(response.body().asReader()); // 简单处理,直接逗号分隔 return Arrays.asList(resultStr.split(",")).iterator(); .target(DecoderClient.class, "http://localhost:8080"); Stream<String> result = client.getDemo1List(); result.forEach(System.out::println); }
运行该程序,正常输出:
[DecoderClient#getDemo1List] ---> GET http://localhost:8080/feign/demo1/list HTTP/1.1 [DecoderClient#getDemo1List] ---> END HTTP (0-byte body) [DecoderClient#getDemo1List] <--- HTTP/1.1 200 (37ms) [DecoderClient#getDemo1List] ["A","B","C"] //server返回的内容 [DecoderClient#getDemo1List] <--- END HTTP (13-byte body) // 打印如下: "C"]
这里因为只做模拟,所以对
iterator
解码器的实现非常简陋。实际生产中其实这个解码器使用极少,毕竟 几乎没有人会在Client端用Stream类型去作为返回值 吧,用集合类型即可。OptionalDecoder
支持Java8的
java.util.Optional
,例子略。StringDecoder
顾名思义,值处理返回值类型String类型的方法。
public class StringDecoder implements Decoder { @Override public Object decode(Response response, Type type) throws IOException { // 1、如果body为null,那就return null喽 Response.Body body = response.body(); if (body == null) { return null; // 2、仅处理String类型:把body流转换为String // 注意:这里asReader()没有指定编码,默认使用的是UTF8哦~~~~ if (String.class.equals(type)) { return Util.toString(body.asReader()); // 3、若不是String类型,报错... throw new DecodeException(response.status(), format("%s is not a type supported by this decoder.", type), response.request()); }
它有一个子类:Default。
Default
顾名思义: 它是Feign默认使用的解码器 。它继承自
StringDecoder
,所以也仅只能解码为字符串类型:public class Default extends StringDecoder { @Override public Object decode(Response response, Type type) throws IOException { // 404状态码和204的特殊处理:返回值只和type类型有关,和Response无关 if (response.status() == 404 || response.status() == 204) return Util.emptyValueOf(type); if (response.body() == null) return null; // 不仅支持String,同时也支持到了返回值为字节数组的情况... // 说明:字节流和编码无关,字符流才有关 if (byte[].class.equals(type)) { return Util.toByteArray(response.body().asInputStream()); // 处理字符串 return super.decode(response, type); }
它增加了父类,能对404进行特殊处理,且支持到了字节数组
byte[]
类型的返回值。 由此可以这么认为:Feign默认情况下只支持String类型的返回值,且List<String>
这种它默认是不支持的 。ResponseMappingDecoder
它也是
feign.codec.Decoder
接口的实现类之一,但是我单独把它摘出来是因为它相对特殊。Feign: static class ResponseMappingDecoder implements Decoder { private final ResponseMapper mapper; private final Decoder delegate; // 代理 ... // 省略构造器 // decode动作交给delegate去做,只不过在这之前完成一个mapper操作 @Override public Object decode(Response response, Type type) throws IOException { return delegate.decode(mapper.map(response, type), type); }
ResponseMappingDecoder
它是一个Feign的内部类,并且是非public的,所以只能在Feign本类内使用。它和上面实现的不同点在于:
- 实际解码动作交给
delegate
完成- 在执行decode动作 之前 ,执行一次
ResponseMapper #map()
映射动作所以它是一个代理实现,并且有点在解码之前做一层 拦截 的意思。
ResponseMapper
是个函数式接口,它的巧用在后续章节里会介绍。 该解码器唯一使用处:Feign.Builder: public Builder decoder(Decoder decoder) { this.decoder = decoder; return this; public Builder mapAndDecode(ResponseMapper mapper, Decoder decoder) { this.decoder = new ResponseMappingDecoder(mapper, decoder); return this; }
这个是Builder的一个方法,需要你在构建的时候 手动调用 才会生效,可以理解为
Builder#decoder()
方法的增强版。 注意:这两个方法请互斥调用,因为都调用木有意义(后者会覆盖前者)。ErrorDecoder
顾名思义,它是发生错误、异常情况时使用的解码器,允许你对异常进行特殊处理。官方给出了一个示例实现,对它的作用一语中的:
class IllegalArgumentExceptionOn404Decoder implements ErrorDecoder { @Override public Exception decode(String methodKey, Response response) { if (response.status() == 400) throw new IllegalArgumentException("bad zone name"); // 如果不是400,还是交给Default去解码处理 // 改进:这里使用单例即可,Default不用每次都去new return new ErrorDecoder.Default().decode(methodKey, response); }
这个示例就允许我们自己去定制对400错误码的解码方式。通常情况下404在Http响应码中具有较强语义的,因此默认情况下它是抛出异常,当然你可通过
feign.Feign.Builder#decode404()
来定制。说明:若开启decode404,那么它交给的是
Decoder
去完成,而非ErrorDecoder
public interface ErrorDecoder { // 从Response最终解码出的必须是个异常类型 // methodKey:由feign.Feign#configKey生成 Exception decode(String methodKey, Response response); }
它只有唯一的一个实现类,也是缺省实现。
Default
Feign的缺省实现,也是唯一实现。
public static class Default implements ErrorDecoder { @Override public Exception decode(String methodKey, Response response) { // 根据状态码提取出异常类型,并且同意包装为FeignException // 4xx:FeignClientException // 5xx:FeignServerException // 其它:new FeignException( ... ) FeignException exception = errorStatus(methodKey, response); // 检查,是否需要把异常类型包装为RetryableException(也就是看看是重试失败的,还是非重试失败的...) Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER)); if (retryAfter != null) { return new RetryableException( response.status(), exception.getMessage(), response.request().httpMethod(), exception, retryAfter, response.request()); return exception;