本系列代码地址:https://github.com/JoJoTec/sp...

首先,咱们给出官网文档中的组件结构图:

官网文档中的组件,是以实现性能为维度的,咱们这里是以源码实现为维度的(因为之后咱们应用的时候,须要依据须要定制这些组件,所以须要从源码角度去拆分剖析),可能会有一些小差别。

负责解析类元数据的 Contract

OpenFeign 是通过代理类元数据来主动生成 HTTP API 的,那么到底解析哪些类元数据,哪些类元数据是无效的,是通过指定 Contract 来实现的,咱们能够通过实现这个 Contract 来自定义一些类元数据的解析,例如,咱们自定义一个注解:

//仅可用于办法上@java.lang.annotation.Target(METHOD)//指定注解放弃到运行时@Retention(RUNTIME)@interface Get {    //申请 uri    String uri();}

这个注解很简略,标注了这个注解的办法会被主动封装成 GET 申请,申请 uri 为 uri() 的返回。

而后,咱们自定义一个 Contract 来解决这个注解。因为 MethodMetadata 是 final 并且是 package private 的,所以咱们只能继承 Contract.BaseContract 去自定义注解解析:

//内部自定义必须继承 BaseContract,因为外面生成的 MethodMetadata 的结构器是 package private 的static class CustomizedContract extends Contract.BaseContract {    @Override    protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {        //解决类下面的注解,这里没用到    }    @Override    protected void processAnnotationOnMethod(MethodMetadata data, Annotation annotation, Method method) {        //解决办法下面的注解        Get get = method.getAnnotation(Get.class);        //如果 Get 注解存在,则指定办法 HTTP 申请形式为 GET,同时 uri 指定为注解 uri() 的返回        if (get != null) {            data.template().method(Request.HttpMethod.GET);            data.template().uri(get.uri());        }    }    @Override    protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {        //解决参数下面的注解,这里没用到        return false;    }}

而后,咱们来应用这个 Contract:

interface HttpBin {    @Get(uri = "/get")    String get();}public static void main(String[] args) {    HttpBin httpBin = Feign.builder()            .contract(new CustomizedContract())            .target(HttpBin.class, "http://www.httpbin.org");    //实际上就是调用 http://www.httpbin.org/get    String s = httpBin.get();}

个别的,咱们不会应用这个 Contract,因为咱们业务上个别不会自定义注解。这是底层框架须要用的性能。比方在 spring-mvc 环境下,咱们须要兼容 spring-mvc 的注解,这个实现类就是 SpringMvcContract

编码器 Encoder 与解码器 Decoder

编码器与解码器接口定义:

public interface Decoder {  Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;}public interface Encoder {  void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;}

OpenFeign 能够自定义编码解码器,咱们这里应用 FastJson 自定义实现一组编码与解码器,来理解其中应用的原理。

/** * 基于 FastJson 的反序列化解码器 */static class FastJsonDecoder implements Decoder {    @Override    public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {        //读取 body        byte[] body = response.body().asInputStream().readAllBytes();        return JSON.parseObject(body, type);    }}/** * 基于 FastJson 的序列化编码器 */static class FastJsonEncoder implements Encoder {    @Override    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {        if (object != null) {            //编码 body            template.header(CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());            template.body(JSON.toJSONBytes(object), StandardCharsets.UTF_8);        }    }}

而后,咱们通过 http://httpbin.org/anything 来测试,这个链接会返回咱们发送的申请的所有元素。

interface HttpBin {    @RequestLine("POST /anything")    Object postBody(Map<String, String> body);}public static void main(String[] args) {    HttpBin httpBin = Feign.builder()            .decoder(new FastJsonDecoder())            .encoder(new FastJsonEncoder())            .target(HttpBin.class, "http://www.httpbin.org");    Object o = httpBin.postBody(Map.of("key", "value"));}

查看响应,能够看到咱们发送的 json body 被正确的接管到了。

目前,OpenFeign 我的项目中的编码器以及解码器次要实现包含:

序列化须要额定增加的依赖实现类
间接转换成字符串,默认的编码解码器feign.codec.Encoder.Defaultfeign.codec.Decoder.Default
gsonfeign-gsonfeign.gson.GsonEncoderfeign.gson.GsonDecoder
xmlfeign-jaxbfeign.jaxb.JAXBEncoderfeign.jaxb.JAXBDecoder
json (jackson)feign-jacksonfeign.jackson.JacksonEncoderfeign.jackson.JacksonDecoder

咱们在 Spring Cloud 环境中应用的时候,在 Spring MVC 中是有对立的编码器以及解码器的,即 HttpMessageConverters,并且通过胶水我的项目做了兼容,所以咱们对立用 HttpMessageConverters 指定自定义编码解码器就好。

申请拦截器 RequestInterceptor

RequestInterceptor 的接口定义:

public interface RequestInterceptor {  void apply(RequestTemplate template);}

能够从接口看出,RequestInterceptor 其实就是对于 RequestTemplate 进行额定的操作。对于每次申请,都会通过所有的 RequestInterceptor 解决。

举个例子,咱们能够对于每个申请加上特定的 Header:

interface HttpBin {    //发到这个链接的所有申请,响应会返回申请中的所有元素    @RequestLine("GET /anything")    String anything();}static class AddHeaderRequestInterceptor implements RequestInterceptor {    @Override    public void apply(RequestTemplate template) {        //增加 header        template.header("test-header", "test-value");    }}public static void main(String[] args) {    HttpBin httpBin = Feign.builder()            .requestInterceptor(new AddHeaderRequestInterceptor())            .target(HttpBin.class, "http://www.httpbin.org");    String s = httpBin.anything();}

执行程序,能够在响应中看到咱们发送申请中增加的 header。

Http 申请客户端 Client

OpenFeign 底层的 Http 申请客户端是能够自定义的,OpenFeign 针对不同的 Http 客户端都有封装,默认的是通过 Java 内置的 Http 申请 API。咱们来看下 Client 的接口定义源码:

public interface Client {  /**   * 执行申请   * @param request HTTP 申请   * @param options 配置选项   * @return   * @throws IOException   */  Response execute(Request request, Options options) throws IOException;}

Request 是 feign 中对于 Http 申请的定义,Client 的实现须要将 Request 转换成对应底层的 Http 客户端的申请并调用适合的办法进行申请。Options 是一些申请通用配置,包含:

public static class Options {    //tcp 建设连贯超时    private final long connectTimeout;    //tcp 建设连贯超时工夫单位    private final TimeUnit connectTimeoutUnit;    //申请读取响应超时    private final long readTimeout;    //申请读取响应超时工夫单位    private final TimeUnit readTimeoutUnit;    //是否追随重定向    private final boolean followRedirects;}

目前,Client 的实现包含以下这些:

底层 HTTP 客户端须要增加的依赖实现类
Java HttpURLConnectionfeign.Client.Default
Java 11 HttpClientfeign-java11feign.http2client.Http2Client
Apache HttpClientfeign-httpclientfeign.httpclient.ApacheHttpClient
Apache HttpClient 5feign-hc5feign.hc5.ApacheHttp5Client
Google HTTP Clientfeign-googlehttpclientfeign.googlehttpclient.GoogleHttpClient
Google HTTP Clientfeign-googlehttpclientfeign.googlehttpclient.GoogleHttpClient
jaxRSfeign-jaxrs2feign.jaxrs2.JAXRSClient
OkHttpfeign-okhttpfeign.okhttp.OkHttpClient
Ribbonfeign-ribbonfeign.ribbon.RibbonClient

谬误解码器相干

能够指定谬误解码器 ErrorDecoder,同时还能够指定异样抛出策略 ExceptionPropagationPolicy.

ErrorDecoder 是读取 HTTP 响应判断是否有谬误须要抛出异样应用的:

public interface ErrorDecoder {    public Exception decode(String methodKey, Response response);}

只有响应码不为 2xx 的时候,才会调用配置的 ErrorDecoderdecode 办法。默认的 ErrorDecoder 的实现是:

public static class Default implements ErrorDecoder {    @Override    public Exception decode(String methodKey, Response response) {      //将不同响应码包装成不同的异样      FeignException exception = errorStatus(methodKey, response);      //提取 Retry-After 这个 HTTP 响应头,如果存在这个响应头则将异样封装为 RetryableException      //对于 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;    }  }

能够看出, ErrorDecoder 是可能给异样封装一层异样的,这有时候对于咱们在外层捕获会造成影响,所以能够通过指定 ExceptionPropagationPolicy 来拆开这层封装。ExceptionPropagationPolicy 是一个枚举类:

public enum ExceptionPropagationPolicy {  //什么都不做  NONE,   //是否将 RetryableException 的原始 exception 提取进去作为异样抛出  //目前只针对 RetryableException 失效,调用 exception 的 getCause,如果不为空就返回这个 cause,否则返回原始 exception  UNWRAP,  ;}

接下来看个例子:

interface TestHttpBin {    //申请肯定会返回 500    @RequestLine("GET /status/500")    Object get();}static class TestErrorDecoder implements ErrorDecoder {    @Override    public Exception decode(String methodKey, Response response) {        //获取错误码对应的 FeignException        FeignException exception = errorStatus(methodKey, response);        //封装为 RetryableException        return new RetryableException(                response.status(),                exception.getMessage(),                response.request().httpMethod(),                exception,                new Date(),                response.request());    }}public static void main(String[] args) {    TestHttpBin httpBin = Feign.builder()            .errorDecoder(new TestErrorDecoder())            //如果这里没有指定为 UNWRAP 那么上面抛出的异样就是 RetryableException,否则就是 RetryableException 的 cause 也就是 FeignException            .exceptionPropagationPolicy(ExceptionPropagationPolicy.UNWRAP)            .target(TestHttpBin.class, "http://httpbin.org");    httpBin.get();}

执行后能够发现抛出了 feign.FeignException$InternalServerError: [500 INTERNAL SERVER ERROR] during [GET] to [http://httpbin.org/status/500] [TestHttpBin#get()]: [] 这个异样。

针对 RetryableException 的重试器 Retryer

在调用产生异样的时候,咱们可能心愿依照肯定策略进行重试,形象这种重试策略个别包含:

  • 对于哪些异样会重试
  • 什么时候重试,什么时候完结重试,例如重试 n 次当前

对于那些异样会重试,这个由 ErrorDecoder 决定。如果异样须要被重试,就把它封装成 RetryableException,这样 Feign 就会应用 Retryer 进行重试。对于什么时候重试,什么时候完结重试,这些就是 Retryer 须要思考的事件:

public interface Retryer extends Cloneable {  /**    * 判断持续重试,或者抛出异样完结重试    */  void continueOrPropagate(RetryableException e);  /**    * 对于每次申请,都会调用这个办法创立一个新的同样配置的 Retryer 对象    */  Retryer clone();}

咱们来看一下 Retryer 的默认实现:

class Default implements Retryer {    //最大重试次数    private final int maxAttempts;    //初始重试距离    private final long period;    //最大重试距离    private final long maxPeriod;    //以后重试次数    int attempt;    //以后曾经期待的重试间隔时间和    long sleptForMillis;    public Default() {      //默认配置,初始重试距离为 100ms,最大重试距离为 1s,最大重试次数为 5      this(100, SECONDS.toMillis(1), 5);    }    public Default(long period, long maxPeriod, int maxAttempts) {      this.period = period;      this.maxPeriod = maxPeriod;      this.maxAttempts = maxAttempts;      //以后重试次数从 1 开始,因为第一次进入 continueOrPropagate 之前就曾经产生调用然而失败了并抛出了 RetryableException      this.attempt = 1;    }    // visible for testing;    protected long currentTimeMillis() {      return System.currentTimeMillis();    }    public void continueOrPropagate(RetryableException e) {      //如果以后重试次数大于最大重试次数则      if (attempt++ >= maxAttempts) {        throw e;      }      long interval;      //如果指定了 retry-after,则以这个 header 为准决定等待时间      if (e.retryAfter() != null) {        interval = e.retryAfter().getTime() - currentTimeMillis();        if (interval > maxPeriod) {          interval = maxPeriod;        }        if (interval < 0) {          return;        }      } else {        //否则,通过 nextMaxInterval 计算        interval = nextMaxInterval();      }      try {        Thread.sleep(interval);      } catch (InterruptedException ignored) {        Thread.currentThread().interrupt();        throw e;      }      //记录一共期待的工夫      sleptForMillis += interval;    }    //每次重试距离增长 50%,直到最大重试距离    long nextMaxInterval() {      long interval = (long) (period * Math.pow(1.5, attempt - 1));      return interval > maxPeriod ? maxPeriod : interval;    }    @Override    public Retryer clone() {      //复制配置      return new Default(period, maxPeriod, maxAttempts);    }}

默认的 Retryer 性能也比拟丰盛,用户能够参考这个实现更适宜本人业务场景的重试器。

每个 HTTP 申请的配置 Options

无论是哪种 HTTP 客户端,都须要如下几个配置:

  • 连贯超时:这个是 TCP 连贯建设超时工夫
  • 读取超时:这个是收到 HTTP 响应之前的超时工夫
  • 是否追随重定向

OpenFeign 能够通过 Options 进行配置:

public static class Options {    private final long connectTimeout;    private final TimeUnit connectTimeoutUnit;    private final long readTimeout;    private final TimeUnit readTimeoutUnit;    private final boolean followRedirects;}

例如咱们能够这么配置一个连贯超时为 500ms,读取超时为 6s,追随重定向的 Feign:

Feign.builder().options(new Request.Options(    500, TimeUnit.MILLISECONDS, 6, TimeUnit.SECONDS, true))

咱们这一节具体介绍了 OpenFeign 的各个组件,有了这些常识,其实咱们本人就能实现 Spring-Cloud-OpenFeign 外面的胶水代码。其实 Spring-Cloud-OpenFeign 就是将这些组件以 Bean 的模式注册到 NamedContextFactory 中,供不同微服务进行不同的配置。

微信搜寻“我的编程喵”关注公众号,每日一刷,轻松晋升技术,斩获各种offer