[享学Feign] 六、原生Feign的解码器Decoder、ErrorDecoder

你现在没有决定的权利,但你有决定未来的权利

–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/feign-learning

前言

上文 介绍了Feign的编码器Encoder,本篇继续了解它的解码器Decoder,以及错误解码器ErrorDecoder


正文

编码器作用于Request,那么解码器作用于Response,用于解析Http请求的响应,提取有用信息数据。


解码器Decoder

将HTTP响应feign.Response解码为指定类型的单一对象。当然触发它也有前提:

  1. 响应码是2xx
  2. 方法返回值既不是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)

// 打印如下:
["A"
"B"
"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;
    }
	...
}

错误解码器逻辑相对简单,case其实很简单:根据Http响应码来判断抛出对应的异常类型
若你想定制处理特殊的状态码or异常类型,个人建议使用Default兜底(就是eg所为)。


总结

关于Feign的解码器、错误解码器就介绍到这了。本文内容并不难,但因为使用的原因,平时很少会接触到这块,所以可能会让你觉得陌生。
本文帮你打通编码、解码的知识点,这样你在理解Spring Cloud和它的集成,以及定制它的行为将变得更加的游刃有余。
分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

发布了377 篇原创文章 · 获赞 572 · 访问量 51万+
展开阅读全文

微服务 引入feign 远程调用服务接口报错feign.FeignException: status 406 reading xxx#xxxx()

03-29

2019-03-29 17:32:11.075 ERROR 22168 --- [io-41001-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.FeignException: status 406 reading TaskInterface#test()] with root cause feign.FeignException: status 406 reading TaskInterface#test() at feign.FeignException.errorStatus(FeignException.java:62) ~[feign-core-9.5.1.jar:na] at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:91) ~[feign-core-9.5.1.jar:na] at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) ~[feign-core-9.5.1.jar:na] at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) ~[feign-core-9.5.1.jar:na] at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103) ~[feign-core-9.5.1.jar:na] at com.sun.proxy.$Proxy86.test(Unknown Source) ~[na:na] at com.shiyunxin.feign.TaskController.test(TaskController.java:23) ~[classes/:na] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_91] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_91] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_91] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_91] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209) ~[spring-web-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) ~[spring-web-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:635) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) ~[tomcat-embed-websocket-8.5.31.jar:8.5.31] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109) ~[spring-web-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) ~[spring-web-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496) [tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803) [tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790) [tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1468) [tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.31.jar:8.5.31] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_91] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_91] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.31.jar:8.5.31] at java.lang.Thread.run(Thread.java:745) [na:1.8.0_91] 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie

分享到微信朋友圈

×

扫一扫,手机浏览