小工具      在线工具  汉语词典  css  js  c++  java

【Spring Cloud阿里巴巴】(三)OpenFeign扩展点实践+详细源码讲解

Spring Cloud Alibaba 微服务实战,java,微服务,spring cloud,spring,云原生 额外说明

收录于:40天前

CSDN成就一亿技术人

系列目录

【Spring Cloud阿里巴巴】(一)微服务介绍及Nacos注册中心实际实现
【Spring Cloud阿里巴巴】(二)微服务调用组件Feign原理+实战



前言

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


一、Feign扩展点配置

在上文,我们主要讲解了架构图的上部和下部,本文主要针对架构图中间扩展部分
在这里插入图片描述

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

  • 日志级别日志级别
  • 契约合同
  • 客户端客户
  • 超时设置选项
  • 编码器编码器
  • 解码器解码器
  • 拦截器请求拦截器

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

例如:

@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阿里巴巴】(二)微服务调用组件Feign原理+实战

我这里准备了3个Spring Cloud Alibaba微服务:演示-a复员演示-c,之所以准备3个服务是为了验证配置是全局有效还是局有效!
OpenFeign扩展点Demo的3个服务

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

1. 通过配置文件配置

应用程序属性格式:

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

有效范围说明

  • 全局生效:配置 {服务名称}默认 ,如上面例子中所示
  • 局部生效:配置 {服务名称}具体服务名称
    例如,下面的配置仅对调用demo-b服务有效。
  feign.client.config.demo-b.loggerLevel = BASIC

验证是否生效

你知道如何快速验证吗?

一一尝试一下? ---教你一个简单有效的方法:

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

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

  • 配置后的Feign.Builder,确认已经按应用程序属性配置:

Feign.Builder查看调试属性-已配置

  • 如果未配置,默认Feign.Builder如下:

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
    Java Bean配置后的Feign.Builder

我已确认它是否在全球或本地有效。既然不好演示,有兴趣的话可以自己验证一下!

补充说明1. 日志级别

假装提供了4种日志级别

日志级别 简单解释
没有任何 默认值,不记录日志
基本的 记录请求方法、请求URL、响应状态码、执行时间
标头 在 BASIC 级别上记录请求和响应标头
满的 记录所有日志:请求和响应的标头、正文和元数据

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

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

在这里插入图片描述

补充说明2. 契约contract

OpenFeign下,大多数情况下不需要配置contract,但是如果老项目已经定义了大量feign注解,那么就没有必要改成SpringMvc注解了。直接改合同不失为一个好办法!

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

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

补充说明3. 编解码器

使用Jackson,需要引入依赖:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-jackson</artifactId>
</dependency>

使用Gson,需要引入依赖:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-gson</artifactId>
</dependency>

Gson应用程序属性配置:

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

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

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

补充说明4. 拦截器

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

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

Feign默认提供了基本身份验证拦截器,我们可以直接配置使用:

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

另外,我们也可以自定义 ,只需要实现接口请求拦截器

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时,可以使用拦截器来实现。当然,这不仅仅是为了添加标题!下面的GZIP压缩就是通过拦截器实现的!

补充说明5. 配置Client

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

在OpenFeign中,默认客户端是JDK原生的URLConnection,接下来,我们就实战 快速配置Apache HttpClient好的http

1). 配置Client为Apache HttpClient

  • 引入依赖
<!-- Apache HttpClient-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>
<!-- Feign集成Apache HttpClient -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
  • 在配置文件中启用

应用程序属性

feign.httpclient.enabled=true

参考源码: FeignAutoConfiguration.HttpClientFeignConfiguration
OpenFeign源码:FeignAutoConfiguration.HttpClientFeignConfiguration源码头部

验证已生效:
Feign验证已配置Client为HttpClient

2). 配置Client为OkHttp

  • 引入依赖
<!-- okhttp -->
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
</dependency>
<!-- Feign集成okhttp -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>
  • 在配置文件中启用

应用程序属性

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

参考源码: FeignAutoConfiguration.OkHttpFeignConfiguration
OpenFeign源码:FeignAutoConfiguration.OkHttpFeignConfiguration源码头部

验证已生效:
Feign验证已配置Client为OkHttp

补充说明6. 配置GZIP压缩

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

应用程序属性

# 请求数据压缩
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
OpenFeign源码:FeignClientEncodingProperties头部
压缩条件判断逻辑:

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

OpenFeign源码:GZIP压缩条件判断逻辑

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

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

  • 参考源码: FeignContentGzipEncodingAutoConfiguration
    OpenFeign源码: FeignContentGzipEncodingAutoConfiguration

  • 参考源码:FeignAcceptGzipEncodingAutoConfiguration
    OpenFeign源码:FeignAcceptGzipEncodingAutoConfiguration

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

小结

配置项 应用程序属性 Java配置对象
日志级别日志级别 feign.client.config.default.loggerLevel = BASIC @Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.BASIC;
}
契约合同 feign.client.config.default.contract = feign.Contract.Default @Bean
public Contract feignContract() {
return new Contract.Default();
}
超时设置选项 feign.client.config.default.connectTimeout =5000
feign.client.config.default.readTimeout =30000
@Bean
public Request.Options options() {
return new Request.Options(5000, 30000);
}
编解码器编码器&解码器 feign.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();
}
拦截器请求拦截器 #是数组,可以按下标配置多个
feign.client.config.default.requestInterceptors[0]=
com.tiangang.demo.api.interceptor.MyFeignRequestInterceptor
@Bean
public MyFeignRequestInterceptor myFeignRequestInterceptor() {
return new MyFeignRequestInterceptor();
}
客户 #配置为ApacheHttpClient, 别忘了引入依赖
feign.httpclient.enabled=true

#配置为OkHttp, 别忘了引入依赖
feign.httpclient.enabled=false
feign.okhttp.enabled=true
广州邮政编码压缩 #当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整合的惯用套路,所以我们快速跟踪一下主线流程,看它是如何将应用程序编程接口生成的动态代理类:

  • FeignClient注册商
    @EnableFeignClients上有@Import(FeignClientsRegistrar.class)注解,
    OpenFeign源码:EnableFeignClients
    FeignClientsRegistrar重写的registerBeanDefinitions方法里会扫描所有@FeignClient的接口,并将所有接口注册为FeignClientFactoryBean
    OpenFeign源码:FeignClientsRegistrar扫描@FeignClient注解的接口并注册
  • FeignClientFactoryBean
    FeignClientFactoryBean重写getObject方法,先通过feign方法获取到Feign.Builder,再根据FeignClient.url决定是否走负载均衡loadBalance,不管怎么走,最终都会调用Feign.Builder.target方法生成动态代理对象。
    OpenFeign源码:FeignClientFactoryBean.getObject和getTarget

2. 扩展点配置主逻辑

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

配置Feign方法:
在这里插入图片描述

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

  • 1.配置Java Bean

  • 2.配置属性的默认配置(全局)

  • 3.配置当前服务的属性配置(部分)

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

3. 配置文件源头

主逻辑中的FeignClientProperties特性 就是应用程序属性配置文件项的源头,带有@ConfigurationProperties注解,如下图:
OpenFeign源码:FeignClientProperties

从这里就可以看出:具体每项配置是地图类型的配置,它的钥匙=服务名价值=FeignClientConfiguration

所以在应用程序属性里配置,均为feign.client.config.{服务名}.{配置名}={配置值}

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

FeignClientConfiguration中所有可配置的属性如下:
OpenFeign源码: FeignClientConfiguration中全部可配置的属性

4. Java Bean配置源头

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

  • FeignClientFactoryBean.feign方法-构建
    这里配置所需豆类,下图这5个都有在FeignClients配置里配置缺省Bean,当然,如果你在指定的FeignConfig中加了自定义@Bean,就会以你配置的为准!
    OpenFeign源码: FeignClientFactoryBean.feign方法 - 构建
  • FeignClientFactoryBean.configureUsingConfiguration方法-配置
    主要在这里配置可选Bean,就是下图这些 建设者。 设置的,当然,如果你在指定的FeignConfig中加了自定义@Bean,就会以你配置的为准!
    OpenFeign源码: FeignClientFactoryBean.configureUsingConfiguration方法 - 配置

最后

通过本文,我们对OpenFeign的扩展点配置进行了实战,并对源码进行了详细解读,如果你在项目中使用到了OpenFeign,相信这些扩展功能会让你在项目中使用得心应手。 另外需要说明:OpenFeign不仅可以用于微服务之间的调用,还可以用于调用第三方服务,所以应用非常广泛!
至此,OpenFeign的神秘面纱就被我们完全揭开了!
那么接下来,在Spring Cloud Alibaba家族中,还有一位主打高性能 RPC 调用的组件,就是由阿里巴巴公司开源的,后捐献给Apache 基金会的达博,那么它到底有什么过人之处,会让很多公司从Feign转到Dubbo调用?这也是我计划将在下面分享的内容,如果感觉不错,欢迎订阅本专栏,后面还有更多的【Spring Cloud 阿里巴巴】实用知识陆续放出。

关注我 天罡格 分享更多干货: https://blog.csdn.net/scm_2008
大家的「关注❤️+点赞-+收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!


. . .

相关推荐

额外说明

Guns启动项目抛出:脚本错误、flyway执行迁移异常

利用IDEA启动Guns项目的时候,控制台报了一下错误。  1、问题复现过程 (1)第一步:首先用IDEA把guns项目导入 (2)第二步:创建一个数据库:guns (3) 第三步:导入数据库脚本 (4) 第四步:修改数据库链接默认账号和密码 (5) 第

额外说明

MyBatis参数对象包含List集合

问题: MyBatis传参对象包含List集合。 dto实体类: public class InfoDto { private String dnnName; private List<String> userLabels; } da

额外说明

Java多线程wait()和notify()系列方法使用教程(内幕)

简介 本文讲解Java中wait()、notify(),通过一个标准的使用实例,来讨论下这两个方法的作用和使用时注意点,这两个方法被提取到顶级父类Object对象中,地位等同于toString()方法,所以本文带你从零开始搞懂它们的用法,在文章最后,准备

额外说明

23种设计模式详解与示例代码(详解附DEMO)

设计模式在Java中的应用与实现 ---1.创建型模式 1. 工厂方法模式(Factory Pattern) 2.抽象工厂模式(Abstract Factory Pattern) 3. 单例模式(Singleton Pattern) 4.原型模式(Pro

额外说明

【Python 千题 —— 基础篇】列表最大值

题目描述 题目描述 给定一个包含数字的列表,编写一个程序,从列表中获取并输出最大的数字。 输入描述 输入一个包含若干数字的列表。 输出描述 程序将计算并输出列表中的最大值。 示例 示例 ① 1,2,3,2,4,8,3 输出: 8 代码讲解 下面是本题

额外说明

memcached系列2:memcached实例

 在上一篇文章,我们讲了,为什么要使用memched做为缓存服务器(没看的同学请 点这里)。下面让我们以memcached-1.2.1-win32版本的服务组件(安装后是以一个windows服务做daemon)和C#API(Enyim.Caching)为

额外说明

第三模块 面向对象编程&网络&并发编程

第三模块 面向对象&网络&并发编程 从今天开始,我们将进入系列课程第3个模块的的学习,此模块包含如下三大部分知识: 面向对象,Python中支持两种编程方式来写代码,分别是:函数式编程、面向对象式编程。 函数式 # 定义函数,在函数中实现功能 def f

额外说明

关于SQL server 中无效列的解决办法

先放下我遇到此问题的截图吧: 看到报错的第一反应是建表方法错了,于是我又按照书上的建表方法再次建表,依然报错。再然后我网上百度了一些办法:重启、刷新都无济于事。然后我又静下来,慢慢检查代码,后面发现外键的对应关键字长度不一致,于是将其长度改为一致就解决问

额外说明

解决因缺少MSVBVM50.DLL无法启动问题

其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题,如果是新手第一时间会认为是软件或游戏出错了,其实并不是这样,其主要原因就是你电脑系统的该dll文件丢失了或者损坏了,这时你只需下载这个MSVBVM50.DLL文件进行安装(前提是找到适合的版本),

额外说明

如何在WordPress网站上禁用嵌入

WordPress 4.4 introduced a post oEmbed feature which allows others to embed your WordPress posts into their own site by adding

ads via 小工具