文章目录

一、前言

在前面的文章:

  1. ​SpringCloud之Feign实现声明式客户端负载均衡详细案例​
  2. ​SpringCloud之OpenFeign实现服务间请求头数据传递(OpenFeign拦截器RequestInterceptor的使用)​
  3. ​SpringCloud之OpenFeign的常用配置(超时、数据压缩、日志)​
  4. ​SpringCloud之OpenFeign的核心组件(Encoder、Decoder、Contract)​
  5. ​SpringBoot启动流程中开启OpenFeign的入口(ImportBeanDefinitionRegistrar详解)​
  6. ​源码剖析OpenFeign如何扫描所有的FeignClient​

我们聊了以下内容:

  1. OpenFeign的概述、为什么会使用Feign代替Ribbon?
  2. Feign和OpenFeign的区别?
  3. 详细的OpenFeign实现声明式客户端负载均衡案例
  4. OpenFeign中拦截器RequestInterceptor的使用
  5. OpenFeign的一些常用配置(超时、数据压缩、日志输出)
  6. SpringCloud之OpenFeign的核心组件(Encoder、Decoder、Contract)
  7. 在SpringBoot启动流程中开启OpenFeign的入口
  8. OpenFeign如何扫描 / 注册所有的FeignClient

本文基于OpenFeign低版本(​​SpringCloud 2020.0.x版本之前​​)讨论:OpenFeign如何为FeignClient生成动态代理类

PS:本文基于的SpringCloud版本

<properties>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>

</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring cloud alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

在上一篇文章(​​SpringCloud之深度源码剖析OpenFeign如何扫描所有的FeignClient​​)我们聊了OpenFeign如何扫描所有的FeignClient,本文接着聊OpenFeign是如何针对FeignClient生成动态代理类的?

0、生成FeignClient的动态代理类整理流程图

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_微服务

二、生成FeignClient的动态代理类

这里依旧是结合OpenFeign案例( ​​SpringCloud之Feign实现声明式客户端负载均衡详细案例​​)聊,ServiceBController中通过@Autowired注解注入了一个被@FeignClient标注的接口ServiceAClient。

在​​AbstractApplicationContext#refresh()​​​方法中最后调用的​​finishBeanFactoryInitialization(beanFactory)​​​方法中会将所有类全部注入到Spring容器中;在将ServiceBController注入到Spring容器过程中,会将其成员ServiceAClient也注入到Spring容器中,​​AbstractAutowireCapableBeanFactory#populateBean()​​​方法会处理ServiceAClient,进而调用到​​AutowiredAnnotationBeanPostProcessor#postProcessProperties()​​方法对ServiceAClient做一个注入操作,具体执行流程如下:

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_云原生_02


【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_云原生_03


…中间经过一些过程(买个坑,@AutoWired自动注入实现机制后面特地写一篇文章分析)…走到​​AbstractBeanFactory#getBean(String)​​方法获取ServiceBController依赖的成员类型ServiceAClient;

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_云原生_04

由于我们在注册FeignClient到Spring容器时,构建的BeanDefinition的beanClas是​​FeignClientFactoryBean​​;

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_微服务_05


FeignClientFactoryBean是一个工厂,保存了@FeignClient注解的所有属性值,在Spring容器初始化的过程中,其会根据之前扫描出的FeignClient信息构建FeignClient的动态代理类。

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_微服务_06


从debug的堆栈信息我们可以看到是​​FeignClientFactoryBean#getObject()​​方法负责获取/创建动态代理类。

假如我们不debug,该如何找到哪里负责创建动态代理类?

1、FeignClientFactoryBean创建动态代理类的入口

我们知道要通过注册到Spring容器中的FeignClient的BeanDefinition的beanClass属性是FeignClientFactoryBean,所大概率和​​FeignClientFactoryBean​​是相关的,那怎么找呢?

高工、架构们选择连蒙带猜!!!!

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_spring_07

注意到​​FeignClientFactoryBean的feign(FeignContext)​​​方法,方法会构造一个​​Feign.Builder​​,Builder、Builder,这不是就是构造器模式嘛;基于Feign.Builder可以构造对应的FeignClient。

再看哪里调用了​​feign()​​​方法;找到​​getTarget()​​方法;

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_spring_08

对于有一定开发的经验而言,见到Target这一类东西,基本可以确定就是动态代理;

再往上追,看哪里调用了​​getTarget()​​​方法?进入到​​getObject()​​方法;

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_spring cloud_09


而getObject()方法是FactoryBean接口中定义的方法;

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_spring cloud_10

到这里可以确定​​FeignClientFactoryBean#getObject()​​方法,在spring容器初始化时,会被作为入口来调用,进而创建一个ServiceAClient的动态代理,返回给spring容器 并 注册到Spring容器里去。

2、Feign.Builder的构建过程

上面我们得出结论:FeignClient是通过Feign.Builder来构建的,生成FeignClient动态代理的入口是​​FeignClientFactoryBean#getObject()​​,这里我们看一下Feign.Builder是如何构建的?

1)FeignContext上下文的获取

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_微服务_11

调用​​FeignClientFactoryBean#getObject()​​创建/获取FeignClient动态代理类时,首先要通过

FeignContext context = applicationContext.getBean(FeignContext.class);

获取Feign的上下文​​FeignContext​​。

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_动态代理_12


这里的​​applicationContext​​​是​​AnnotationConfigServletWebApplicationContext​​。

在Ribbon系列中我们聊了ribbon里有一个​​SpringClientFactory​​,就是对每个服务的调用,都会有一个独立的ILoadBalancer,IoadBalancer里面的IRule、IPing都是独立的组件,也就是说ribbon要调用的每个服务都对应一个独立的spring容器;从那个独立的spring容器中,可以取出某个服务关联的属于自己的LoadBalancer、IRule、IPing等。

FeignContext

我们如果要调用一个服务的话,ServiceA,那么那个服务(ServiceA)就会关联一个独立的spring容器;关联着自己独立的一些组件,比如说独立的Logger组件,独立的Decoder组件,独立的Encoder组件;FeignContext则代表了一个独立的容器工厂,里面记录了每个服务对应的容器​​AnnotationConfigApplicationContext​​。

  • 因此,可以对不同的@FeignClient自定义不同的Configuration。

FeignContext在哪里注入到Spring容器的?

FeignContext位于​​spring-cloud-openfeign-core​​​项目,我们在这个项目下结合SpringBoot自动装配的特性找XxxAutoConfiguration 或 XxxConfiguration,最终找到​​FeignAutoConfiguration​​。

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_spring cloud_13

FeignAutoConfiguration中使用@Bean方法将FeignContext注入到Spring容器;

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_云原生_14

FeignContext继承自​​NamedContextFactory​​​,内部负责对每个服务都维护一个对应的spring容器(以map存储,一个服务对应一个spring容器);此处和Ribbon一样,可以参考Ribbon的文章(​​SpringCloud之Ribbon和服务注册中心的集成细节​​)。

进入到​​feign()​​方法中,以获取FeignLoggerFactory为例:

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_微服务_15


get(FeignContext context, Class type)方法要做的事情如下:

  • 根据服务名称(ServiceA)去FeignContext里面去获取对应的FeignLoggerFactory;
  • 其实就是根据ServiceA服务名称,先获取对应的spring容器,然后从那个spring容器中,获取自己独立的一个FeignLoggerFactory;

默认使用的​​FeignLoggerFactory​​​是在​​spring-cloud-openfeign-core​​​项目的FeignClientsConfiguration类中加载的​​DefaultFeignLoggerFactory​​​,而​​DefaultFeignLoggerFactory​​​中默认创建的是​​Slf4jLogger​​;

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_动态代理_16

2)从FeignContext中获取Feign.Builder

这里和上面获取FeignLoggerFactory一样,在​​spring-cloud-openfeign-core​​​项目的FeignClientsConfiguration类中会找到两个​​Feign.Builder​​(一个和Hystrix相关,另外一个Retryer相关的(请求超时、失败重试))的注册逻辑:

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_动态代理_17


由于默认​​feign.hystrix.enabled​​​属性为false,所以默认注入的Feign.Builder是​​Feign.builder().retryer(retryer)​​。

3)处理配置信息

回到​​feign()​​​方法,其中调用的​​configureFeign(context, builder)​​方法负责处理Feign的相关配置(即:使用application.yml中配置的参数,来设置Feign.Builder)。

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_spring cloud_18


【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_spring_19

逻辑解析:

  1. ​FeignClientProperties​​是针对FeignClient的配置;
  2. 先读取application.yml中的feign.client打头的一些参数,包括了connectionTimeout、readTimeout之类的参数;如果application.yml中没有配置feign.client相关参数,则使用默认配置(Retryer retryer、ErrorDecoder、Request.Options等);
  3. 然后读取application.yml中针对当前要调用服务的配置;

所以如果在application.yml文件中同时配置了针对全部服务和单个服务的配置,则针对单个服务的配置优先级最高,因为在代码解析中它是放在后面解析的,会覆盖调前面解析的内容。

4)使用Feign.Builder构建出一个FeignClient

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_spring_20


如果在@FeignClient上,没有配置​​url属性​​​,也就是没有指定服务的url地址;那么Feign就会自动跟ribbon关联起来,采用ribbon来进行负载均衡,直接拿出@FeignClient中配置的name()为Ribbon准备对应的url地址:​​http://ServiceA​​;

此外如果在@FeignClient注解中配置了​​path属性​​​,就表示要访问的是这个ServiceA服务的某一类接口,比如:@FeignClient(value = “ServiceA”, path = “/user”),在拼接请求URL地址的时候,就会拼接成:​​http://ServiceA/user​​。

​FeignClientFactoryBean#loadBalance()​​方法是一个基于ribbon进行负载均衡的FeignClient动态代理生成方法;

其入参包括:

  1. Feign.Builder builder --> FeignClient构造器
  2. FeignContext context --> Feign上下文
  3. ​HardCodedTarget<T>​​​ target,​​target​​是一个HardCodedTarget,硬编码的Target,里面包含了接口类型(com.zhss.service.ServiceAClient)、服务名称(ServiceA)、url地址(http://ServiceA)

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_动态代理_21


​loadBalance()​​方法中首先会获取Client 和 Targeter;

  • 通过​​Client client = getOptional(context, Client.class)​​​方法获取Client,返回的是​​LoadBalancerFeignClient​​,(高版本是FeignBlockingLoadBalancerClient);
  • 通过​​Targeter targeter = get(context, Targeter.class)​​​方法获取Targeter,返回的是​​HystrixTargeter​​;

下面我们来看一下LoadBalancerFeignClient 和 HystrixTargeter是在哪里注入到Spring容器的?

1> LoadBalancerFeignClient在哪里注入到Spring容器?

进入到LoadBalancerFeignClient类中,看哪里调用了它唯一一个构造函数;

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_spring_22

找到LoadBalancerFeignClient发现有三个地方调用了它的构造函数,new了一个实例;

  • DefaultFeignLoadBalancedConfiguration
  • HttpClientFeignLoadBalancedConfiguration
  • OkHttpFeignLoadBalancedConfiguration

再结合默认的配置,只有​​DefaultFeignLoadBalancedConfiguration​​中的Client符合条件装配;

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_云原生_23


可以通过引入Apache HttpClient的maven依赖使用HttpClientFeignLoadBalancedConfiguration,

或引入OkHttpClient的maven依赖并在application.yml文件中指定​​feign.okhttp.enabled​​属性为true使用OkHttpFeignLoadBalancedConfiguration。

2> HystrixTargeter在哪里注入到Spring容器?

在​​FeignAutoConfiguration​​​类中可以找到​​Targeter​​注入到Spring容器的逻辑;

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_spring cloud_24

默认是创建HystrixTargeter;

  1. 如果有feign.hystrix.HystrixFeign这个类的话,那么就会构造一个HystrixTargeter出来;
  2. 如果没有feign.hystrix.HystrixFeign这个类的话,那么就会构造一个DefaultTargeter出来;

HystrixTargeter是用来让feign和hystrix整合使用的,在发送请求的时候可以基于hystrix实现熔断、限流、降级。

  • 生产环境如果启用​​feign.hystrix.enabled​​​,则Feign.Builder也会变成​​HystrixFeign.Builder​​​,默认还是Feign自己的​​Feign.Builder​​;
3> HystrixTargeter#target()方法

继续往下走,进入到​​HystrixTargeter#target()​​方法,具体代码执行流程如下:

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_spring cloud_25

Feign#target()方法中主要做两件事:

  1. ​build()​​方法将Feign.Builder中所有东西集成在一起,构建成一个ReflectiveFeign;
  2. ​ReflectiveFeign#newInstance()​​方法负责生成动态代理;
4> ReflectiveFeign#newInstance()生成动态代理类

​ReflectiveFeign#newInstance()​​源代码如下:

@Override
public <T> T newInstance(Target<T> target) {
// 基于我们配置的Contract、Encoder等一堆组件,加上Target对象(知道是ServiceAClient接口),去进行接口的所有spring mvc注解的解析,以及接口中各个方法的一些解析,获取了这个接口中有哪些方法
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

// 遍历ServiceAClient接口中的每个方法,
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
// 将ServiceAClient接口中的每个方法,加上对应的nameToHandler中存放的对应的SynchronousMethodHandler(异步化的方法代理处理组件),放到一个map中去,
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}

// (JDK动态代理)基于一个factory工厂,创建了一个InvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
// 基于JDK的动态代理,创建出来了一个动态代理类:Proxy,其实现ServiceAClient接口
// new Class<?>[]{target.type()},这个就是ServiceAClient接口
// InvocationHandler:对上面proxy动态代理类所有方法的调用,都会走这个InvocationHandler的拦截方法,由这个InvocationHandler中的一个方法来提供所有方法的一个实现的逻辑。
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);

for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}

方法中有两个Map类型的局部变量:nameToHandler、methodToHandler;

  1. nameToHandler释义:接口中的每个方法的名称,对应一个处理这个方法的​​SynchronousMethodHandler​​;由ReflectiveFeign的内部类ParseHandlersByName的apply(target)方法获取。
  2. methodToHandler释义:接口中的每个方法(Method对象),对应一个处理这个方法的​​SynchronousMethodHandler​​;

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_云原生_26


上述代码段中,就是JDK动态代理的体现;动态生成一个没有名字的匿名类,这个类实现了​​ServiceAClient(FeignClient)​​​接口,基于这个匿名的类创建一个对象(T proxy),这就是所谓的动态代理;后续所有对这个​​T proxy​​​对象所有接口方法的调用,都会交给​​InvocationHandler​​​来处理,此处的InvocationHandler是​​ReflectiveFeign的内部类FeignInvocationHandler​​。

下面我们继续看​​ReflectiveFeign的内部类ParseHandlersByName​​的apply(target)方法如何解析FeignClient中的方法?

<1> FeignClient接口和MethodHandler的映射map生成机制流程图

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_动态代理_27

<2> ParseHandlersByName#apply()解析FeignClient中的方法

​ParseHandlersByName#apply()​​方法会对我们定义的ServiceAClient接口进行解析,解析里面有哪些方法,然后为每个方法创建一个SynchronousMethodHandler出来,也就是说某个SynchronousMethodHandler专门用来处理那个方法的请求调用。

apply()方法逻辑如下:

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_spring cloud_28


其中 (1)​factory.create()​​​会为所有标注了SpringMvc注解的方法都生成一个对应的​​SynchronousMethodHandler​​。

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_云原生_29


(2)​SpringMvcContract.parseAndValidateMetadata()​​​方法负责解析FeignClient接口中每个标注了SpringMVC注解的方法;即:Feign依靠Contract组件(​​SpringMvcContract​​)来解析接口上的spring mvc注解;

针对FeignClient接口中的每个标注了SpringMVC注解的方法都会被SpringMvcContract组件解析,针对每个方法最后都生成一个MethodMetadata,代表方法的一些元数据,包括:

  1. 方法的定义,比如:ServiceAClient#deleteUser(Long)
  2. 方法的返回类型,比如:class java.lang.String
  3. 发送HTTP请求的模板,比如:DELETE /user/{id} HTTP/1.1
5> SpringMvcContract组件的工作原理

这里看一下​​SpringMvcContract.parseAndValidateMetadata()​​是如何解析FeignClient中的每个方法。

以如下方法为例:

【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何为FeignClient生成动态代理类_spring cloud_30

示例解析逻辑如下:

  1. 解析@RequestMapping注解,看看里面的method属性是什么?是GET/UPDATE/DELETE,然后在HTTP template里就加上GET/UPDATE/DELETE;(示例为DELETE)
  2. 找到接口上定义的@RequestMapping注解,解析里面的value值,拿到请求路径(/user),此时HTTP template变成:DELETE /user;
  3. 再次解析deleteUser()方法上的@RequestMapping注解,找到里面的value,获取到/{id},拼接到HTTP template里去:DELETE /user/{id}
  4. 接着硬编码拼死一个HTTP协议,http 1.1,HTTP template:DELETE /user/{id} HTTP/1.1
  5. indexToName:解析@PathVariable注解,第一个占位符(index是0)要替换成方法入参里的id这个参数的值。
  6. 假如后面来调用这个deleteUser()方法,传递进来的id = 1.那么此时就会拿出之前解析好的HTTP template:DELETE /user/{id} HTTP/1.1。然后用传递进来的id = 1替换掉第一个占位符的值,DELETE /user/1 HTTP/1.1

三、总结和后续文章

本文我们聊了OpenFeign如何为FeignClient生成动态代理类,SpringMvcContract组件如何解析FeignClient中标注了SpringMVC注解的方法。

下篇文章我们接着聊OpenFeign接收到一个请求如何处理?