SpringCloud系列:FeignClient源码深度解析
概述
springCloud feign主要对netflix feign进行了增强和包装,本篇从源码角度带你过一遍装配流程,揭开feign底层的神秘面纱。主要包括feign整合ribbon,hystrix,sleuth,以及生成的代理类最终注入到spring容器的过程。篇幅略长,耐心读完,相信你会有所收获。
Feign架构图
一些核心类及大致流程:
大体步骤: 一、注册FeignClient配置类和FeignClient BeanDefinition 二、实例化Feign上下文对象FeignContext 三、创建Feign.builder 对象四、生成负载均衡代理类五、生成默认代理类六、注入到spring容器
源码分析
主要围绕上面6个步骤详细分析。
一、注册FeignClient配置类和FeignClient BeanDefinition
从启动类注解开始,来看下 @EnableFeignClients
注解:
1. @EnableFeignClients
2. public
class
MyApplication
{
3. }
这是在启动类开启feign装配的注解,跟进该注解,看看做了什么:
1. @Import
(
FeignClientsRegistrar
.
class
)
1. public
class
FeignClientsRegistrar
implements
ImportBeanDefinitionRegistrar
,
2.
ResourceLoaderAware
,
BeanClassLoaderAware
{
3.
4.
// patterned after Spring Integration IntegrationComponentScanRegistrar
5.
// and RibbonClientsConfigurationRegistgrar
6.
private
final
Logger
logger =
LoggerFactory
.getLogger(
FeignClientsRegistrar
.
class
);
7.
private
ResourceLoader
resourceLoader;
8.
9.
private
ClassLoader
classLoader;
10.
11.
public
FeignClientsRegistrar
() {
12. }
13.
14.
@Override
15.
public
void
setResourceLoader(
ResourceLoader
resourceLoader) {
16.
this
.resourceLoader = resourceLoader;
17. }
18.
19.
@Override
20.
public
void
setBeanClassLoader(
ClassLoader
classLoader) {
21.
this
.classLoader = classLoader;
22. }
23.
24.
@Override
25.
public
void
registerBeanDefinitions(
AnnotationMetadata
metadata,
26.
BeanDefinitionRegistry
registry) {
27.
//1
、先注册默认配置
28. registerDefaultConfiguration(metadata, registry);
29.
//2
、注册所有的
feignClient beanDefinition
30. registerFeignClients(metadata, registry);
31. }
32.
//...
33.}
我们分别来看一下上面 registerBeanDefinitions
中的两个方法: 1)注册默认配置方法: registerDefaultConfiguration
:
1.
private
void
registerDefaultConfiguration(
AnnotationMetadata
metadata,
2.
BeanDefinitionRegistry
registry) {
3.
Map
<
String
,
Object
> defaultAttrs = metadata
4. .getAnnotationAttributes(
EnableFeignClients
.
class
.getName(),
true
);
5.
6.
if
(defaultAttrs !=
null
&& defaultAttrs.containsKey(
"defaultConfiguration"
)) {
7.
String
name;
8.
if
(metadata.hasEnclosingClass()) {
9. name =
"default."
+ metadata.getEnclosingClassName();
10. }
11.
else
{
12. name =
"default."
+ metadata.getClassName();
13. }
14.
// name
默认以
default
开头,后续会根据名称选择配置
15. registerClientConfiguration(registry, name,
16. defaultAttrs.
get
(
"defaultConfiguration"
));
17. }
18. }
上述方法为读取启动类上面 @EnableFeignClients
注解中声明feign相关配置类,默认name为default,一般情况下无需配置。用默认的 FeignAutoConfiguration
即可。上面有个比较重要的方法:注册配置 registerClientConfiguration
,启动流程一共有两处读取feign的配置类,这是第一处。根据该方法看一下:
1.
private
void
registerClientConfiguration(
BeanDefinitionRegistry
registry,
Object
name,
2.
Object
configuration) {
3.
BeanDefinitionBuilder
builder =
BeanDefinitionBuilder
4. .genericBeanDefinition(
FeignClientSpecification
.
class
);
5. builder.addConstructorArgValue(name);
6. builder.addConstructorArgValue(configuration);
7. registry.registerBeanDefinition(
8. name +
"."
+
FeignClientSpecification
.
class
.getSimpleName(),
9. builder.getBeanDefinition());
10. }
上面将bean配置类包装成 FeignClientSpecification
,注入到容器。该对象非常重要,包含FeignClient需要的重试策略,超时策略,日志等配置,如果某个服务没有设置,则读取默认的配置。
2、扫描FeignClient
该方法主要是扫描类路径,对所有的FeignClient生成对应的 BeanDefinition
:
1. public
void
registerFeignClients(
AnnotationMetadata
metadata,
2.
BeanDefinitionRegistry
registry) {
3.
4.
//...
5.
//
获取扫描目录下面所有的
bean deanDefinition
6.
for
(
String
basePackage : basePackages) {
7.
Set
<
BeanDefinition
> candidateComponents = scanner
8. .findCandidateComponents(basePackage);
9.
for
(
BeanDefinition
candidateComponent : candidateComponents) {
10.
if
(candidateComponent
instanceof
AnnotatedBeanDefinition
) {
11.
// verify annotated class is an interface
12.
AnnotatedBeanDefinition
beanDefinition = (
AnnotatedBeanDefinition
) candidateComponent;
13.
AnnotationMetadata
annotationMetadata = beanDefinition.getMetadata();
14.
Assert
.isTrue(annotationMetadata.isInterface(),
15.
"@FeignClient can only be specified on an interface"
);
16.
17.
Map
<
String
,
Object
> attributes = annotationMetadata
18. .getAnnotationAttributes(
19.
FeignClient
.
class
.getCanonicalName());
20.
21.
String
name = getClientName(attributes);
22.
//
这里是第二处
23. registerClientConfiguration(registry, name,
24. attributes.
get
(
"configuration"
));
25.
26.
//
注册
feignClient
27. registerFeignClient(registry, annotationMetadata, attributes);
28. }
29. }
30. }
31. }
可以看到上面又调用了 registerClientConfiguration
注册配置的方法,这里是第二处调用。这里主要是将扫描的目录下,每个项目的配置类加载的容器当中。 注册到容器中,什么时候会用到呢?具体又如何使用呢?别着急,后面会有介绍。
我们先会回到继续主流程,继续看注册feignClient的方法,跟进 registerFeignClient
:
1. private
void
registerFeignClient(
BeanDefinitionRegistry
registry,
2.
AnnotationMetadata
annotationMetadata,
Map
<
String
,
Object
> attributes) {
3.
String
className = annotationMetadata.getClassName();
4.
//
声明代理类名称
5.
BeanDefinitionBuilder
definition =
BeanDefinitionBuilder
6. .genericBeanDefinition(
FeignClientFactoryBean
.
class
);
7.
//logger.info("TEX do some replacement");
8.
//attributes.put("value", ((String)attributes.get("value")).replace('_','-'));
9. validate(attributes);
10. definition.addPropertyValue(
"url"
, getUrl(attributes));
11. definition.addPropertyValue(
"path"
, getPath(attributes));
12.
String
name = getName(attributes);
13. definition.addPropertyValue(
"name"
, name);
14. definition.addPropertyValue(
"type"
, className);
15. definition.addPropertyValue(
"decode404"
, attributes.
get
(
"decode404"
));
16. definition.addPropertyValue(
"fallback"
, attributes.
get
(
"fallback"
));
17. definition.setAutowireMode(
AbstractBeanDefinition
.AUTOWIRE_BY_TYPE);
18.
19.
String
alias
= name +
"FeignClient"
;
20.
AbstractBeanDefinition
beanDefinition = definition.getBeanDefinition();
21. beanDefinition.setPrimary(
true
);
22.
BeanDefinitionHolder
holder =
new
BeanDefinitionHolder
(beanDefinition, className,
23.
new
String
[] {
alias
});
24.
//
将
bean definition
加入到
spring
容器
25.
BeanDefinitionReaderUtils
.registerBeanDefinition(holder, registry);
26. }
划重点,上面出现了一行相当关键代码:
1. BeanDefinitionBuilder
definition =
BeanDefinitionBuilder
.genericBeanDefinition(
FeignClientFactoryBean
.
class
);
springCloud FeignClient其实是利用了spring的代理工厂来生成代理类,所以这里将所有的 feignClient
的描述信息 BeanDefinition
设定为 FeignClientFactoryBean
类型,该类又继承 FactoryBean
,很明显,这是一个代理类。在spring中, FactoryBean
是一个工厂bean,用作创建代理bean,所以得出结论,feign将所有的feignClient bean包装成 FeignClientFactoryBean
。扫描方法到此结束。
代理类什么时候会触发生成呢?在spring刷新容器时,当实例化我们的业务service时,如果发现注册了FeignClient,spring就会去实例化该FeignClient,同时会进行判断是否是代理bean,如果为代理bean,则调用 FeignClientFactoryBean
的 T getObject() throws Exception;
方法生成代理bean。
先来隆重介绍一下 FeignClientFactoryBean
,后面四步都基于此类。
先看一下代理feignClient代理生成入口: getObject
方法:
1. @Override
2.
public
Object
getObject()
throws
Exception
{
3.
//
二、实例化
Feign
上下文对象
FeignContext
4.
FeignContext
context = applicationContext.getBean(
FeignContext
.
class
);
5.
//
三、生成
builder
对象,用来生成
feign
6.
Feign
.
Builder
builder = feign(context);
7.
8.
//
判断生成的代理对象类型,如果
url
为空,则走负载均衡,生成有负载均衡功能的代理类
9.
if
(!
StringUtils
.hasText(
this
.url)) {
10.
String
url;
11.
if
(!
this
.name.startsWith(
"http"
)) {
12. url =
"http://"
+
this
.name;
13. }
14.
else
{
15. url =
this
.name;
16. }
17. url += cleanPath();
18.
//
四、生成负载均衡代理类
19.
return
loadBalance(builder, context,
new
HardCodedTarget
<>(
this
.type,
20.
this
.name, url));
21. }
22.
//
如果指定了
url
,则生成默认的代理类
23.
if
(
StringUtils
.hasText(
this
.url) && !
this
.url.startsWith(
"http"
)) {
24.
this
.url =
"http://"
+
this
.url;
25. }
26.
String
url =
this
.url + cleanPath();
27.
//
五、生成默认代理类
28.
return
targeter.target(
this
, builder, context,
new
HardCodedTarget
<>(
29.
this
.type,
this
.name, url));
30. }
getObject()
逻辑比较多,每一行都会做一些初始化配置,来逐步分析。
二、实例化Feign上下文对象FeignContext
上述方法中第一行便是实例化 FeignContext
:
1. FeignContext
context = applicationContext.getBean(
FeignContext
.
class
);
获取 FeignContext
对象,如果没有实例化,则主动实例化,如下:
可以看到feign的配置类设置到feign的容器当中,而集合中的元素正是上面我们提到的两处调用 registerClientConfiguration
方法添加进去的,前后呼应。
然而,当我们引入了 sleuth
之后,获取的 feignContext
确是 TraceFeignClientAutoConfiguration
中配置的实例 sleuthFeignContext
:
可以看到上面创建了一个 TraceFeignContext
实例,因为该对象继承 FeignContext
,同时又加了 @Primary
注解,所以在上面第2步中通过类型获取: applicationContext.getBean(FeignContext.class);
,最终拿到的是 TraceFeignContext
。
三、构造 FeignBuilder
继续跟进该方法:
上述代码有两处逻辑,分别来看:
1、 Feign.Builder builder = get(context, Feign.Builder.class)
,又会有以下三种情况:
1)单独使用Feign,没有引入 sleuth
、 hystrix
:通过加载FeignClientsConfiguration的配置创建 Feign
的静态内部类: Feign.Builder
1.
@Bean
2.
@Scope
(
"prototype"
)
3.
@ConditionalOnMissingBean
4.
public
Feign
.
Builder
feignBuilder(
Retryer
retryer) {
5.
return
Feign
.builder().retryer(retryer);
6. }
2)引入了 hystrix
,没有引入 sleuth
: 通过加载 FeignClientsConfiguration
的配置创建 HystrixFeign
的静态内部类: HystrixFeign.Builder
1.
@Configuration
2.
@ConditionalOnClass
({
HystrixCommand
.
class
,
HystrixFeign
.
class
})
3.
protected
static
class
HystrixFeignConfiguration
{
4.
@Bean
5.
@Scope
(
"prototype"
)
6.
@ConditionalOnMissingBean
7.
@ConditionalOnProperty
(name =
"feign.hystrix.enabled"
, matchIfMissing =
false
)
8.
public
Feign
.
Builder
feignHystrixBuilder() {
9.
return
HystrixFeign
.builder();
10. }
11. }
3)同时引入 hystrix
和 sleuth
: 加载 TraceFeignClientAutoConfiguration
的配置创建: HystrixFeign.Builder
:
注意:
· TraceFeignClientAutoConfiguration
的配置类加载一定是在 FeignClientsConfiguration
之前(先加载先生效),而 FeignClientsConfiguration
加载是通过 FeignAutoConfiguration
完成的,所以上图中引入了条件注解:
1. @AutoConfigureBefore
({
FeignAutoConfiguration
.
class
})
· 创建创建的 builder
对象和第二种情况一下,只是做了一层包装:
1.
final
class
SleuthFeignBuilder
{
2.
3.
private
SleuthFeignBuilder
() {}
4.
5.
static
Feign
.
Builder
builder(
Tracer
tracer,
HttpTraceKeysInjector
keysInjector) {
6.
return
HystrixFeign
.builder()
7.
//
各组件
`client
,
retryer
,
decoder`
进行增强,装饰器模式。
8. .client(
new
TraceFeignClient
(tracer, keysInjector))
9. .retryer(
new
TraceFeignRetryer
(tracer))
10. .decoder(
new
TraceFeignDecoder
(tracer))
11. .errorDecoder(
new
TraceFeignErrorDecoder
(tracer));
12. }
13.}
2、设置重试策略,log等组件Feign.builder在获取之后又分别指定了重试策略,日志级别,错误代码code等,在上一步中调用 SleuthFeignBuilder.build()
时已经设置过默认值了,这里为什么要重复设置呢?
我们跟进去get()方法,一探究竟:
1.
protected
<T> T
get
(
FeignContext
context,
Class
<T> type) {
2.
//
根据
name
,也就是服务名称来生成
builder
3. T instance = context.getInstance(
this
.name, type);
4.
if
(instance ==
null
) {
5.
throw
new
IllegalStateException
(
"No bean found of type "
+ type +
" for "
6. +
this
.name);
7. }
8.
return
instance;
9. }
1.
public
<T> T getInstance(
String
name,
Class
<T> type) {
2.
//
这里获取
AnnotationConfigApplicationContext
容器
3.
AnnotationConfigApplicationContext
context = getContext(name);
4.
if
(
BeanFactoryUtils
.beanNamesForTypeIncludingAncestors(context,
5. type).length >
0
) {
6.
return
context.getBean(type);
7. }
8.
return
null
;
9. }
10.
11.
private
Map
<
String
,
AnnotationConfigApplicationContext
> contexts =
new
ConcurrentHashMap
<>();
12.
13.
protected
AnnotationConfigApplicationContext
getContext(
String
name) {
14.
if
(!
this
.contexts.containsKey(name)) {
15.
synchronized
(
this
.contexts) {
16.
if
(!
this
.contexts.containsKey(name)) {
17.
//
这里创建容器
createContext(name)
18.
this
.contexts.put(name, createContext(name));
19. }
20. }
21. }
22.
return
this
.contexts.
get
(name);
23. }
重点来了,上述代码将FeignContext做了缓存,每个服务对应一个FeignContext,服务名作为key。继续跟进 createContext(name)
方法:
可以看到上述AnnotationConfigApplicationContext容器并非spring容器,只是利用了spring刷新容器的方法来实例化配置类,以服务名作为key,配置隔离。
重点来了,上面加载配置的顺序为:先加载每个服务的配置类,然后加载启动类注解上的配置类,最后加载默认的配置类。这样做有什么好处?spring刷新容器的方法也是对所有的bean进行了缓存,如果已经创建,则不再实例化。所以优先选取每个FeignClient的配置类,最后默认的配置类兜底。
所以这也证明了 sleuth
的配置一定在 feign
的配置类之前加载。至此, FeignBuilder
构造流程结束。
四、生成负载均衡代理类
再贴一下生成代理类的入口:
1.
//
判断
url
是否为空
2.
if
(!
StringUtils
.hasText(
this
.url)) {
3.
//......
4.
return
loadBalance(builder, context,
new
HardCodedTarget
<>(
this
.type,
5.
this
.name, url));
6. }
7.
//......
8.
return
targeter.target(
this
, builder, context,
new
HardCodedTarget
<>(
9.
this
.type,
this
.name, url));
这里有个重要判断:判断FeignClient声明的url是否为空,来判断具体要生成的代理类。如下:这么做有什么意义?1)如果为空,则默认走Ribbon代理,也就是这个入口,会有加载ribbon的处理。 @FeignClient("MyFeignClient")
2)如果不为空,指定url,则走默认生成代理类的方式,也就是所谓的硬编码。 @FeignClient(value = "MyFeignClient",url = "http://localhost:8081")
这样处理方便开发人员进行测试,无需关注注册中心,直接http调用,是个不错的开发小技巧。
生产环境也可以用上述第二种方式,指定域名的方式。
我们跟进 loadBalance
方法:
1.
protected
<T> T loadBalance(
Feign
.
Builder
builder,
FeignContext
context,
2.
HardCodedTarget
<T> target) {
3.
//
获得
FeignClient
4.
Client
client = getOptional(context,
Client
.
class
);
5.
if
(client !=
null
) {
6. builder.client(client);
7.
return
targeter.target(
this
, builder, context, target);
8. }
9.
throw
new
IllegalStateException
(
10.
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-ribbon?"
);
11. }
Client client = getOptional(context, Client.class);
这里会从 FeignContext
上下文中获取 Client
对象,该对象有三种实例,具体是哪个实现呢?
这里又会有三种情况:1)没有整合 ribbon
、 sleuth
:获取默认的 Client
: Default
实例。
2)整合了 ribbon
,没有整合 sleuth
: 获取 LoadBalanceFeignClient
实例。
3)整合了 ribbon
和 sleuth
: 会获取 TraceFeignClient
实例,该实例是对 LoadBalanceFeignClient
的一种包装,实现方式通过 BeanPostProcessor
实现: FeignBeanPostProcessor
中定义了包装逻辑:
1.
@Override
2.
public
Object
postProcessBeforeInitialization(
Object
bean,
String
beanName)
3.
throws
BeansException
{
4.
return
this
.traceFeignObjectWrapper.wrap(bean);
5. }
通过 wrap
方法最终返回 TraceFeignClient
实例。
继续回到主流程,先来看下 Targeter
接口:
1. interface
Targeter
{
2. <T> T target(
FeignClientFactoryBean
factory,
Feign
.
Builder
feign,
FeignContext
context,
3.
HardCodedTarget
<T> target);
4. }
该对象定义在 FeignClientFactoryBean
静静态代码块中:
1.
private
static
final
Targeter
targeter;
2.
3.
static
{
4.
Targeter
targeterToUse;
5.
//
判断类路径是否引入了
hystrixFeign
6.
if
(
ClassUtils
.isPresent(
"feign.hystrix.HystrixFeign"
,
7.
FeignClientFactoryBean
.
class
.getClassLoader())) {
8. targeterToUse =
new
HystrixTargeter
();
9. }
10.
else
{
11. targeterToUse =
new
DefaultTargeter
();
12. }
13. targeter = targeterToUse;
14. }
这里会初始化 Targeter
,该类是生成feign代理类的工具类,有两种实现,正是上面的 HystrixTargeter
, DefaultTargeter
。因为我们引入了 hystrix
,所以 Targeter
实现类为 HystrixTargeter
。我们继续跟进 targeter.target
方法:
1.
public
<T> T target(
Target
<T> target) {
2.
return
build().newInstance(target);
3. }
上面通过 build()
方法获取生成代理类的工具类 ReflectiveFeign
,再通过 newInstance
正式创建代理类。继续跟进:
1.
public
Feign
build() {
2.
SynchronousMethodHandler
.
Factory
synchronousMethodHandlerFactory =
3.
new
SynchronousMethodHandler
.
Factory
(client, retryer, requestInterceptors, logger,
4. logLevel, decode404);
5.
ParseHandlersByName
handlersByName =
6.
new
ParseHandlersByName
(contract, options, encoder, decoder,
7. errorDecoder, synchronousMethodHandlerFactory);
8.
return
new
ReflectiveFeign
(handlersByName, invocationHandlerFactory);
9. }
这里会创建Feign的方法工厂 synchronousMethodHandlerFactory
, Feign
通过该工厂为每个方法创建一个 methodHandler
,每个 methodHandler
中包含Feign对应的配置: retryer
、 requestInterceptors
等。
继续跟进 newInstance
方法:
1.
public
<T> T newInstance(
Target
<T> target) {
2.
//
创建所有的
MethodHandler
3.
Map
<
String
,
MethodHandler
> nameToHandler = targetToHandlersByName.apply(target);
4.
Map
<
Method
,
MethodHandler
> methodToHandler =
new
LinkedHashMap
<
Method
,
MethodHandler
>();
5.
List
<
DefaultMethodHandler
> defaultMethodHandlers =
new
LinkedList
<
DefaultMethodHandler
>();
6.
7.
for
(
Method
method : target.type().getMethods()) {
8.
if
(method.getDeclaringClass() ==
Object
.
class
) {
9.
continue
;
10.
//
判断是否启用默认
handler
11. }
else
if
(
Util
.isDefault(method)) {
12.
DefaultMethodHandler
handler =
new
DefaultMethodHandler
(method);
13. defaultMethodHandlers.add(handler);
14. methodToHandler.put(method, handler);
15. }
else
{
16. methodToHandler.put(method, nameToHandler.
get
(
Feign
.configKey(target.type(), method)));
17. }
18. }
19.
//
创建
InvocationHandler
,接收请求,转发到
methodHandler
20.
InvocationHandler
handler = factory.create(target, methodToHandler);
21.
//
生成代理类
22. T proxy = (T)
Proxy
.newProxyInstance(target.type().getClassLoader(),
new
Class
<?>[]{target.type()}, handler);
23.
24.
//
将默认方法绑定到代理类
25.
for
(
DefaultMethodHandler
defaultMethodHandler : defaultMethodHandlers) {
26. defaultMethodHandler.bindTo(proxy);
27. }
28.
return
proxy;
29. }
InvocationHandler
最终创建的实例为 HystrixInvocationHandler
,核心方法如下:
1.
HystrixCommand
<
Object
> hystrixCommand =
new
HystrixCommand
<
Object
>(setter) {
2.
@Override
3.
protected
Object
run()
throws
Exception
{
4.
try
{
5.
return
HystrixInvocationHandler
.
this
.dispatch.
get
(method).invoke(args);
6. }
catch
(
Exception
e) {
7.
throw
e;
8. }
catch
(
Throwable
t) {
9.
throw
(
Error
) t;
10. }
11. }
12.
13.
@Override
14.
protected
Object
getFallback() {
15.
//......
16. }
17. };
整个流程:Feign调用方发起请求,发送至hystrix的HystrixInvocationHandler,通过服务名称,找到对应方法的methodHandler,methodHandler中封装了loadBalanceClient、retryer、RequestInterceptor等组件,如果引入了sleuth,这几个组件均是sleuth的包装类。然后通过以上组件构造 http
请求完成整个过程。
五、生成默认代理类
理解了第四步的逻辑,生成默认代理类就很容易理解了,唯一不同点就是 client
的实现类为 loadBalanceClient
。
注意:不管是哪种代理类,最终发起请求还是由 Feign.Default
中的 execute
方法完成,默认使用 HttpUrlConnection
实现。
六、注入spring容器
总结:通过 spring refresh()
方法,触发 FeignClientFactoryBean.getObject()
方法获得了代理类,然后完成注入 spring
容器的过程。该实现方式同 Dubbo
的实现方式类似,有兴趣的可以自行研究噢。
作者:阿康
- 点赞
- 收藏
- 关注作者
评论(0)