【Spring Cloud Alibaba】(三)OpenFeign扩展点实战 + 源码详解

系列目录

【Spring Cloud Alibaba】(一)微服务介绍 及 Nacos注册中心实战

【Spring Cloud Alibaba】(二)微服务调用组件Feign原理+实战


本文目录

  • 系列目录
  • 前言
  • 一、Feign扩展点配置
  • 二、OpenFeign扩展点配置
    • 1. 通过配置文件配置
      • 有效范围说明
      • 验证是否生效
      • 2. 通过Java Bean配置
        • 有效范围说明
        • 验证是否生效
        • 补充说明1. 日志级别
        • 补充说明2. 契约contract
        • 补充说明3. 编解码器
        • 补充说明4. 拦截器
        • 补充说明5. 配置Client
          • 1). 配置Client为Apache HttpClient
          • 2). 配置Client为OkHttp
          • 补充说明6. 配置GZIP压缩
          • 小结
          • 三、源码解读
            • 1. 注册流程
            • 2. 扩展点配置主逻辑
            • 3. 配置文件源头
            • 4. Java Bean配置源头
            • 最后

              前言

              书接上文,我们掌握了Feign的基本使用、核心原理,以及Spring Cloud Alibaba如何快速整合Feign,真的太简单了!你是不是觉得这样就够了?但在实际项目使用OpenFeign时,我们常常会遇到各种需求,需要用到它提供的扩展,例如日志分析、自定义统一拦截器、客户端组件配置、GZIP压缩等等,这也正是我接下来在本文中分享的内容:首先我会从原生Feign扩展点配置入手,然后进行OpenFeign扩展点配置实战,最后对OpenFeign是如何实现的进行了源码解读,内容很详细,Let’s go!


              一、Feign扩展点配置

              在上文,我们主要讲解了架构图的上部和下部,本文主要针对架构图 的 中间扩展部分!

              Feign本身提供了很多扩展点,例如:

              • 日志级别logLevel
              • 契约contract
              • 客户端client
              • 超时设置options
              • 编码器encoder
              • 解码器decoder
              • 拦截器requestInterceptor

                这些扩展点,我们在使用原生Feign时,可以通过Feign.Builder指定,最后再通过target生成动态代理类,完成Bean注册。

                举个例子:

                @Bean
                public UserService userService() { return Feign.builder()
                            .logLevel(Logger.Level.BASIC)
                            .contract(new Contract.Default())
                            .client(new Client.Default(null, null))
                            .encoder(new Encoder.Default())
                            .decoder(new Decoder.Default())
                            .target(UserService.class, "http://demo-b");
                }
                

                二、OpenFeign扩展点配置

                通过上文的OpenFeign实战,我们很容易搭建出Spring Cloud Alibaba微服务框架,并实现服务之间通过OpenFeign调用。如果还未看过上文的同学,建议先看上文:【Spring Cloud Alibaba】(二)微服务调用组件Feign原理+实战

                我这里准备了3个Spring Cloud Alibaba微服务:demo-a、demo-b、demo-c,之所以准备3个服务是为了验证配置是全局有效还是局有效!

                在OpenFeign中扩展配置项,可以通过配置文件和Java Bean两种方式,接下来我们就配置试试看!

                1. 通过配置文件配置

                application.properties,格式:

                feign.client.config.{服务名}.{配置名} = {配置值} 

                我们配置一些你可能用的上的扩展项,比如:日志级别配置、契约配置、超时配置、编解码配置、拦截器配置,如下:

                # 日志级别配置
                feign.client.config.default.loggerLevel = BASIC
                # 契约配置
                feign.client.config.default.contract = feign.Contract.Default
                # 连接超时配置
                feign.client.config.default.connectTimeout = 5000
                # 读取超时配置
                feign.client.config.default.readTimeout = 30000
                # 编码器配置
                feign.client.config.default.encoder = feign.jackson.JacksonEncoder
                # 解码器配置
                feign.client.config.default.decoder = feign.jackson.JacksonDecoder
                # 拦截器配置, 是数组, 需要自定义RequestInterceptor
                feign.client.config.default.requestInterceptors[0]=com.tiangang.demo.c.interceptor.MyFeignRequestInterceptor
                

                有效范围说明

                • 全局生效:配置 {服务名} 为 default ,如上面例子中所示
                • 局部生效:配置 {服务名} 为 具体服务名

                  例如,下面的配置仅对调用demo-b服务有效。

                   feign.client.config.demo-b.loggerLevel = BASIC
                  

                  验证是否生效

                  你知道如何快速验证吗?

                  挨个试?😏😏😏 教你一个简单有效的方法:

                  我使用demo-c发起调用,可以在启动demo-c 启动服务 时,构建 动态代理前 打断点查看Feign.Builder。

                  即在FeignClientFactoryBean.loadBalance方法的调target之前打断点:

                  • 配置后的Feign.Builder,确认已经按application.properties配置:
                    • 如果未配置,默认的Feign.Builder如下:

                      对于是全局还是局部有效,我是确认过的,因为不好演示,所以大家有兴趣可以自行验证确认!

                      2. 通过Java Bean配置

                      通过Java代码配置的话需要定义一个配置类,例如我命名为:FeignConfig,里面定义需要配置的@Bean,与上面配置文件的配置项保持一致!为了做区分,这里将编解码器改为Gson。

                      public class FeignConfig { // 日志级别配置
                          @Bean
                          public Logger.Level feignLoggerLevel() { return Logger.Level.BASIC;
                          }
                          // 契约配置
                          @Bean
                          public Contract feignContract() { return new Contract.Default();
                          }
                          // 超时配置
                          @Bean
                          public Request.Options options() { return new Request.Options(5000, 30000);
                          }
                          // 编解码器配置Jackson
                          /*@Bean
                          public Encoder encoder() {
                              return new JacksonEncoder();
                          }
                          @Bean
                          public Decoder decoder() {
                              return new JacksonDecoder();
                          }*/
                          
                          // 编解码器配置Gson
                          @Bean
                          public Encoder encoder() { return new GsonEncoder();
                          }
                          @Bean
                          public Decoder decoder() { return new GsonDecoder();
                          }
                          // 拦截器配置
                          @Bean
                          public MyFeignRequestInterceptor myFeignRequestInterceptor() { return new MyFeignRequestInterceptor();
                          }
                      }
                      

                      有效范围说明

                      • 全局生效(扫描到的所有服务),两种方式:
                        • 1.在FeignConfig上加@Configuration注解(需要保证能扫描到)
                        • 2.在启动类的@EnableFeignClients注解中配置defaultConfiguration
                          @EnableFeignClients(defaultConfiguration = FeignConfig.class) 
                          • 局部生效(指定服务):在接口API的@FeignClient注解中配置
                            @FeignClient(value = "demo-b", configuration = FeignConfig.class)
                            

                            验证是否生效

                            这里直接到FeignClientFactoryBean.loadBalance方法的target生成动态代理之前打断点查看:

                            • Java Bean配置后的Feign.Builder

                              对于是全局还是局部有效,我是确认过的,因为不好演示,所以大家有兴趣可以自行验证确认!

                              补充说明1. 日志级别

                              Feign提供了4种日志级别:

                              日志级别简单说明
                              NONE默认值,不记录日志
                              BASIC记录请求方法、请求URL、响应状态代码、执行时间
                              HEADERS在BASIC级别的基础上,记录请求和响应的header
                              FULL记录全部日志:请求和响应的header、body和metadata

                              注意: 若要正常输出日志,需要配置接口包路径的日志级别,我这里是com.tiangang.demo.api,所以 application.properties 配置:

                              # 格式:logging.level.{feign接口包路径}=debug/info...
                              logging.level.com.tiangang.demo.api=debug
                              

                              补充说明2. 契约contract

                              在OpenFeign下,大部分情况下不需要配置contract,但如果老项目已经定义了大量的feign注解,那么就没必要再改成SpringMvc注解了,直接改contract是个好办法!

                              OpenFeign的默认contract是SpringMvcContract,即支持SpringMvc注解。

                              如果修改为feign.Contract.Default,测试时别忘了加feign注解,否则会编译报错。

                              补充说明3. 编解码器

                              使用Jackson,需要引入依赖:

                               io.github.openfeign feign-jackson

                              使用Gson,需要引入依赖:

                               io.github.openfeign feign-gson

                              Gson的application.properties配置:

                              feign.client.config.default.encoder = feign.gson.GsonEncoder
                              feign.client.config.default.decoder = feign.gson.GsonDecoder
                              

                              Feign本身还提供了很多编解码器,需要的话可以直接用,如下图:

                              当然了,你也可以自定义编解码器!

                              补充说明4. 拦截器

                              拦截器是 非常有用的扩展点,是我们实现定制化需求的利器!

                              当我们需要统一处理Header、处理请求参数、处理响应结果时,就可以通过自定义拦截器处理。

                              Feign默认提供了Basic 认证拦截器,我们可以直接配置使用:

                              public class FeignConfig { @Bean
                                  public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { return new BasicAuthRequestInterceptor("userName", "password");
                                  }
                              }
                              

                              另外,我们也可以自定义 ,只需要实现接口RequestInterceptor

                              public interface RequestInterceptor { /**
                                 * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
                                 */
                                void apply(RequestTemplate template);
                              }
                              

                              例如,上面的例子我自定义实现的拦截器如下:

                              public class MyFeignRequestInterceptor implements RequestInterceptor { @Override
                                  public void apply(RequestTemplate template) { template.header("ACCESS_KEY", "9ZIpCT02u2ctppiXOzbpwBWMtRKPgxKe");
                                  }
                              }
                              

                              例子的拦截器就是统一加了一个ACCESS_KEY的header,当你的调用需要统一加header时,就可以使用拦截器实现。当然,不仅用于加header!下面的GZIP压缩就是通过拦截器实现的!

                              补充说明5. 配置Client

                              除了上面提到的通用配置方式外,OpenFeign提供了专门的FeignAutoConfiguration,里面包含对Client等的配置,帮助我们快速配置Client。

                              在OpenFeign中,默认的Client是JDK原生的URLConnection,接下来,我们就实战 快速配置 成 Apache HttpClient 和 OkHttp。

                              1). 配置Client为Apache HttpClient

                              • 引入依赖
                                 org.apache.httpcomponents httpclient io.github.openfeign feign-httpclient
                                • 配置文件中启用

                                  application.properties:

                                  feign.httpclient.enabled=true
                                  

                                  参考源码: FeignAutoConfiguration.HttpClientFeignConfiguration

                                  验证已经生效:

                                  2). 配置Client为OkHttp

                                  • 引入依赖
                                     com.squareup.okhttp3 okhttp io.github.openfeign feign-okhttp
                                    • 配置文件中启用

                                      application.properties:

                                      feign.httpclient.enabled=false
                                      feign.okhttp.enabled=true
                                      

                                      参考源码: FeignAutoConfiguration.OkHttpFeignConfiguration

                                      验证已经生效:

                                      补充说明6. 配置GZIP压缩

                                      在大数据量HTTP传输时,开启压缩可以有效节约网络资源,提升接口性能,我们可以配置 GZIP 来压缩数据,这也是OpenFeign通过自定义拦截器为我们实现的扩展功能。

                                      application.properties:

                                      # 请求数据压缩
                                      feign.compression.request.enabled=true
                                      # 压缩类型
                                      feign.compression.request.mimeTypes=text/xml,application/xml,application/json
                                      # 启用压缩的最小大小(默认是2048),这里为了测试配置成1
                                      feign.compression.request.minRequestSize=1
                                      # 响应数据压缩
                                      feign.compression.response.enabled=true
                                      

                                      配置项参考源码: FeignClientEncodingProperties

                                      压缩条件判断逻辑:

                                      • 请求 必须 有header:Content-Type ,并且在配置的mimeTypes中
                                      • 请求 必须 有header:Content-Length,并且大于配置的minRequestSize

                                        我这里准备了一个POST请求,请求json,返回json,日志级别我改为了FULL,验证已经生效:

                                        注意: 只有当Feign的Client 不是 okhttp3.OkHttpClient 的时候,压缩配置才会生效,因为请求和响应的源码中有要求!如下:

                                        • 参考源码: FeignContentGzipEncodingAutoConfiguration

                                        • 参考源码:FeignAcceptGzipEncodingAutoConfiguration

                                          老方法,我们也可以打断点看下GZIP压缩的拦截器:

                                          小结

                                          配置项application.propertiesJava配置对象
                                          日志级别logLevelfeign.client.config.default.loggerLevel = BASIC@Bean
                                          public Logger.Level feignLoggerLevel() {

                                          return Logger.Level.BASIC;
                                          }

                                          契约contractfeign.client.config.default.contract = feign.Contract.Default@Bean
                                          public Contract feignContract() {

                                          return new Contract.Default();
                                          }

                                          超时设置optionsfeign.client.config.default.connectTimeout =5000
                                          feign.client.config.default.readTimeout =30000
                                          @Bean
                                          public Request.Options options() {

                                          return new Request.Options(5000, 30000);
                                          }

                                          编解码器encoder&decoderfeign.client.config.default.encoder = feign.jackson.JacksonEncoder
                                          feign.client.config.default.decoder = feign.jackson.JacksonDecoder
                                          @Bean
                                          public Encoder encoder() {

                                          return new JacksonEncoder();
                                          }
                                          @Bean
                                          public Decoder decoder() {

                                          return new JacksonDecoder();
                                          }

                                          拦截器requestInterceptors#是数组,可以按下标配置多个
                                          feign.client.config.default.requestInterceptors[0]=
                                          com.tiangang.demo.api.interceptor.MyFeignRequestInterceptor
                                          @Bean
                                          public MyFeignRequestInterceptor myFeignRequestInterceptor() {

                                          return new MyFeignRequestInterceptor();
                                          }

                                          Client#配置为ApacheHttpClient, 别忘了引入依赖
                                          feign.httpclient.enabled=true

                                          #配置为OkHttp, 别忘了引入依赖
                                          feign.httpclient.enabled=false
                                          feign.okhttp.enabled=true
                                          GZIP压缩#当Feign的Client 不是 okhttp3.OkHttpClient 的时生效。
                                          # 请求数据压缩
                                          feign.compression.request.enabled=true
                                          # 压缩类型
                                          feign.compression.request.mimeTypes=text/xml,application/xml,application/json
                                          # 启用压缩的最小大小(默认是2048)
                                          feign.compression.request.minRequestSize=2048
                                          # 响应数据压缩
                                          feign.compression.response.enabled=true

                                          三、源码解读

                                          请思考:如果让你来开发设计,你会在哪里做扩展点配置?

                                          我们先跟进下 注册流程,看看能不能找出扩展点配置是在哪里配置的!

                                          1. 注册流程

                                          回顾上文OpenFeign的实战三步走:

                                          • 引入依赖:spring-cloud-starter-openfeign
                                          • 定义远程API接口加@FeignClient注解
                                          • 启动类加@EnableFeignClients注解

                                            通过这三步走,我们可以断定OpenFeign的核心实现:肯定和@EnableFeignClients注解有关,因为没有其它入口了,这也是SpringBoot整合的惯用套路,所以我们快速跟踪一下主线流程,看它是如何将接口API生成的动态代理类:

                                            • FeignClientsRegistrar

                                              在@EnableFeignClients上有@Import(FeignClientsRegistrar.class)注解,

                                              FeignClientsRegistrar重写的registerBeanDefinitions方法里会扫描所有@FeignClient的接口,并将所有接口注册为FeignClientFactoryBean。

                                            • FeignClientFactoryBean

                                              FeignClientFactoryBean重写getObject方法,先通过feign方法获取到Feign.Builder,再根据FeignClient.url决定是否走负载均衡loadBalance,不管怎么走,最终都会调用Feign.Builder.target方法生成动态代理对象。

                                              2. 扩展点配置主逻辑

                                              OK,根据注册流程,主线已经很清晰了,获取到Feign.Builder的地方,正是我们扩展点配置的好地方,实际也确实在这里,如下图:

                                              configureFeign方法:

                                              红框处就是扩展点配置的主逻辑,如果以application.properties为默认(defaultToProperties=true,默认也是true,一般也不会改):

                                              • 1.配置Java Bean

                                              • 2.配置properties的default配置(全局)

                                              • 3.配置properties的当前服务配置(局部)

                                                否则else就反过来!(后配置的优先级自然更高!)

                                                3. 配置文件源头

                                                主逻辑中的FeignClientProperties:properties 就是application.properties配置文件项的源头,带有@ConfigurationProperties注解,如下图:

                                                从这里就可以看出:具体每项配置是Map类型的config,它的key=服务名,value=FeignClientConfiguration

                                                所以在application.properties里配置,均为feign.client.config.{服务名}.{配置名}={配置值}

                                                如果 {服务名} =default,即默认对所有服务有效!否则,仅对配置服务有效!

                                                FeignClientConfiguration中全部可配置的属性如下图:

                                                4. Java Bean配置源头

                                                Java Bean配置,主要在主逻辑的configureUsingConfiguration方法,另外在构建builder时也算一处,一共有两处,优先级由低到高:

                                                • FeignClientFactoryBean.feign方法 - 构建

                                                  这里配置必备Bean,下图这5个都有在FeignClientsConfiguration里配置缺省Bean,当然,如果你在指定的FeignConfig中加了自定义@Bean,就会以你配置的为准!

                                                • FeignClientFactoryBean.configureUsingConfiguration方法 - 配置

                                                  主要在这里配置可选Bean,就是下图这些 builder. 设置的,当然,如果你在指定的FeignConfig中加了自定义@Bean,就会以你配置的为准!


                                                  最后

                                                  通过本文,我们对OpenFeign的扩展点配置进行了实战,并对源码进行了详细解读,如果你在项目中使用到了OpenFeign,相信这些扩展功能会让你在项目中使用得心应手。 另外需要说明:OpenFeign不仅可以用于微服务之间的调用,还可以用于调用第三方服务,所以应用非常广泛!

                                                  至此,OpenFeign的神秘面纱就被我们完全揭开了!

                                                  那么接下来,在Spring Cloud Alibaba家族中,还有一位主打高性能RPC调用的组件,就是由阿里巴巴公司开源的,后捐献给Apache 基金会的Dubbo,那么它到底有什么过人之处,会让很多公司从Feign转到Dubbo调用?这也是我计划将在下文分享的内容,如果感觉不错,欢迎订阅本专栏,后面还有更多的【Spring Cloud Alibaba】实战知识陆续放出。

                                                  关注我 天罡gg 分享更多干货: https://blog.csdn.net/scm_2008

                                                  大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!