Spring Cloud Feign 源码分析 - FeignClientFactoryBean

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud Feign 源码分析 - FeignClientFactoryBean相关的知识,希望对你有一定的参考价值。

参考技术A 关于Feign的启动原理分析,参照另一篇 Spring Cloud Feign 源码分析 - feign启动原理

书接上文,上篇最后提到所有带@FeignClient注解的interface都被封装成FeignClientFactoryBean的BeanDefinition。从名字上可以得知这个类是一个FactoryBean。关于FactoryBean的介绍参考...
因此直接找getObject()。

getTarget方法首先获取FeignContext的对象,基于这个context对当前feign的配置信息存放到Builder中。

首先实例化bean:FeignContext
FeignContext的定义在FeignAutoConfiguration

第一次除了创建新的FeignContext对象之外,还设置了一组configurations,
这组configurations是FeignClientSpecification类型,通过autowired注入。
在扫描EnableFeignClients和各个FeignClient时,将configuration对应的class封装成了FeignClientSpecification的BeanDefinition,这里从容器中取出来创建对象注入到configurations

通过断点可以看到这里有15个FeignClientSpecification的对象

一个是default.开头的在启动类里配置的configuration,剩下的都是FeignClient的configuration。

FeignContext继承了NamedContextFactory,对应的范型就是FeignClientSpecification,看下NamedContextFactory构造方法

这里设置了默认的defaultConfigType,feign里用的是FeignClientsConfiguration,定义了一系列的默认值。

在获取到FeignContext之后,开始封装Feign.Builder。
首先通过context实例化FeignLoggerFactory的对象,因为context是NamedContextFactory的子类,会给每个contextId创建一个独立的AnnotationConfigApplicationContext上下文,每一个k-v会存储在FeignContext的全局context中,key就是contextId

这三个方法的实现完全体现了NamedContextFactory的作用:
给每个name创建一个单独的ApplicationContext子上下文对象,后续凡是这个name的ioc操作,都由独立的ApplicationContext来完成,name之间的context相互隔离。所有的子上下文保存在了Map<String, AnnotationConfigApplicationContext> contexts中。

在创建Context时,补充了configuration的设置:
首先(1的位置),从全局的configurations查找是否定义了只对当前name生效的configuration,也就是判断在当前name所属的FeignClient注解上是否定义了configuration。如果定义过,将这个configuration的Class封装成BeanDefinition注册到本name的子上下文中。

接着(2的位置),从全局的configurations查找是否定义了全局配置,也就是@EnableFeignClients的defaultConfiguration的值,这里固定前缀是default.。
如果也存在,就也将这个defaultConfiguration的Class封装成BeanDefinition注册到本name的子上下文中。

第一次调用完毕get方法后,给每个FeignClient创建的FeignContext就完成了configuration初始化的动作,后面的所有操作,如配置encoder、decoder都是给当前的子上下文内注册BeanDefinition。最后将所有配置封装成Builder返回。

在getTarget()构造完成builder属性之后,开始了整个请求调度过程。

先看第一段:

如果没有url属性,就用name来处理,把http:// + name + path 拼装成url,执行loadBalance()

首先实例化Client的bean对象,默认返回LoadBalancerFeignClient的实例。

从LoadBalancerFeignClient的构造方法可以看到,这里使用了delegate的设计模式来代理Client.Default,扩展execute的实现。

然后则继续实例化Targeter的bean。默认有两种实现类。

我这里返回HystrixTargeter。调用target方法。

这里重点看下feign.target(target)的实现。

可以看到,通过build()构造了一个ReflectiveFeign的对象,将一系列feign的参数封装成了SynchronousMethodHandler和ParseHandlersByName。封装的这两个对象都是为了给后面newInstance用的。

newInstance返回了扩展后的Targeter的代理类。

下面介绍下newInstance的详细过程。

apply方法就是给feignClient的每个方法都封装了一个SynchronousMethodHandler,
factory.create(...)就是为了根据当前方法的各个参数+new SynchronousMethodHandler.Factory定义的默认参数来构造SynchronousMethodHandler
key对应的是类名#方法名,如:MasterClientLocal#getPersons()。
for循环则是为了封装methodToHandler,k-v分别是reflect的Method和SynchronousMethodHandler。遍历完成后,构建一个InvocationHandler的实现类:FeignInvocationHandler

通过传入target和dispatch,其实本质就是在调用SynchronousMethodHandler的invoke方法。而invoke方法则是扩展了http的调用动作,包括请求重试,decode处理,decode404判断等。

最重要的执行则是client.execute(...);
client有两种实现类:

Default是带url的execute的实现,封装了最普通的http调用。
LoadBalanceFeignClient是eureka的实现,通过获取server列表来实现loadBalance。
也就是最开始getTarget() 方法的两段不同的实现过程的最本质区别。

至此,FeignClientFactoryBean的源码分析告一段落。

本人通过delegate方式在此基础上实现了traceId的跨feign传递。将在下一篇文章中做具体说明。

以上是关于Spring Cloud Feign 源码分析 - FeignClientFactoryBean的主要内容,如果未能解决你的问题,请参考以下文章

spring cloud feign遇到的问题

springcloud feign返回Map解析处理

Spring Cloud Feign 调用过程分析

Spring Cloud Feign 调用过程分析

spring cloud kubernetes源码解析之feign与loadbalancer

spring cloud kubernetes源码解析之feign与loadbalancer