前言
在《Hystrix源码浅析》中分析过request cache的相关源码实现,而在Feign中集成了
Ribbon与
Hystrix两个重要组件,但很遗憾其默认设计中并没有对hystrix request cache的实现。故而本章节将从源码出发,了解Feign是如何集成Hystrix组件的,以便后续我们扩展Hystrix在Feign中的应用。正文部分将分为4个阶段分析,为何是4个阶段呢,看了就明白啦。
正文
第一阶段
直接切入正题,找到
FeignClient的自动配置类
FeignClientsConfiguration,编解码器均在当前配置类中设定,我们关注的feign(此处
Feign.Builder最终将帮助我们创建feign实例)客户端定义如下
如上图,可以看到其中有一个内部类
HystrixFeignConfiguration,其注册有两个条件:
- 在classpath中找到HystrixCommand、HystrixFeign类;
- 同时在feignHystrixBuilder方法上可以看到其还需要feign.hystrix.enabled配置为true(如果没有配置,默认值为false);
只有满足了以上两个条件的,方会注册含有ystrix的feign客户端实例,在一个应用中我们会定义多个feignClient,我们需要设定Bean的
scope为
prototype。
反之,如果没有同时满足上述两个条件,将会默认注册一个常规的
FeignBuilder实例。
在源码中还有
FeignAutoConfiguration作为feign的自动配置,
红色框中已经标出了重点,很明显两个自动装配过程通过“
feign.hystrix.HystrixFeign”类是否在classpath中作为判断依据形成了一个互斥约定,贴合本章重点,我们已经加入了Hystrix相关的依赖,故其会自动装配
HystrixTargeter实例。进入
HystrixTargeter类中,其核心实现在
target方法中,
通过4个红色框标记了我们需要分析的四点:
- 根据之前的分析,我们当前装载的builder为HystrixFeign.builder,可直接转型;
- 先记住此处的setterfactory设定,其将在构造HystrixCommand时起到关键作用,如下是一个Default实现:
在Feign类中定义了生成
commandKey的实现,其主要依据
feignclient实例和method生成,由‘类名称#方法名+参数列表中参数类型’组成;
- 优先获取FeignClientFactoryBean中fallback定义,如果获取到则直接通过fallback定义类生成包含回退方法的feignClient代理;
- 如果没有定义fallback类,继续获取FeignClientFactoryBean中fallbackFactory定义,如果获取到则直接通过fallbackFactory定义类生成包含回退方法的feignClient代理;
通过上述的回退方法逻辑我们可以看到
fallback、
fallbackFactory的优先级,
fallback优先级高于
fallbackFactory。
第二阶段
问题来了,
target方法又是何时被调用的呢?我们从启动类中添加的
@EnableFeignClients入手,通过
@EnableFeignClients注解启用声明式客户端应用,
FeignClientsRegistrar实现了
ImportBeanDefinitionRegistrar接口,从而具备了手动注册Bean的能力,从名称上来看所有的
FeignClient实例将通过
FeignClientsRegistrar来实现注册,
如上图核心方法
registerBeanDefinitions在接口中的描述,其中还提及了
BeanDefinitionRegistryPostProcessor,该接口同样可以实现bean的注册以及对已经注册完成的bean进行调整(其主要作用是对已注册bean实现扩展)。直接来看看重写的核心方法
registerBeanDefinitions吧,
其定义了两个步骤,
- 首先通过registerDefaultConfiguration方法设定默认的配置,可以看到其会注册FeignClientSpecification类型实例,究竟这部分会在何时使用呢,后续将会有涉及,
![Feign集成Hystrix源码分析以及扩展_第7张图片](http://img.e-com-net.com/image/info8/912ab9c2cb6a4d319577797f238e9070.png)
- 其次通过registerFeignClients方法来注册声明式客户端feignClient,核心代码如下:
根据上述的断言,我们也可以明白为何声明式客户端的定义是通过接口来完成的,通过对指定的package扫描获取到所有的
@FeignClient
注解定义的
beanDefinition
,如本工程目前配置了6个如下:
继续关注红色框部分,
- 通过@FeignClient注解获取到所有的配置属性以Map方式存储在attributes中。
- 通过registerClientConfiguration(registry, name,attributes.get("configuration"));方法根据configuration属性获取到每个feignClient设定的配置类。
- 通过registerFeignClient(registry, annotationMetadata, attributes);方法注册每个feignClient;
在
registerFeignClient方法中可以看到
@FeignClient注解中所有的参数定义,均将作为后续注册bean实例的属性,并设定自动装配模式为类型装配。
通过最后一个红色框即可完成bean的注册(注册并不代表实例化完成),先来看看其定义,
registerBeanDefinition方法即可完成最后一步注册工作,通过下图来看看
definitionHolder的定义
beanName为接口全名称,并根据上游
registerFeignClient方法获取到了别名,需要关注此处的
beanDefinition中class定义为
FeignClientFactoryBean,这正是
FactoryBean的作用,FactoryBean将通过
getObject生产指定的bean实例,是工厂模式的一个实现。
第三阶段
通过以上完成了每个feignClient对应的
FeignClientFactoryBean注册。既然是通过工厂bean来实例化对应的实例,那么下面我们来看看
getObject()方法又是如何被调用,并生成真实的Bean实例的呢,先来看一段代码
在
Controller中我们通过
@Autowired注入了
Service1FeignClient,在服务启动过程中,
DefaultListableBeanFactory(最核心的bean实例化实现)将实例化
FeignConsumerController的bean实例,其调用链路如下(由下往上)
找到
preInstantiateSingletons方法,被实例化bean描述如下
其中调用了
AbstractBeanFactory的
getBean方法获取当前的
feignConsumerController的bean实例。
DefaultListableBeanFactory类是整个IOC容器初始化的核心,为了后续更好的理解类、方法间的关系,通过下图来了解下
DefaultListableBeanFactory类关系图
接上
getBean()方法继续往下走,找到
DefaultSingletonBeanRegistry中
getSingleton()方法获取单例,可以看到其中有一些cache判断,具体cache定义如下,以map存储为主,beanname作为key值,
如果已经创建对应的实例则会保存在cache中,直接通过
singletonObjects.get(beanName)获取返回即可,首次不存在则需要执行后续逻辑完成创建,找到
AbstractAutowireCapableBeanFactory中
populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw)方法,
其中定义了依赖注入的类型的判断,通过调用的方法堆栈可以看到,在注册完成当前bean后会继续分析其依赖的实例,这也正是依赖注入的核心,在
DefaultListableBeanFactory类中
resolveDependency方法调用
findAutowireCandidates方法找到依赖的候选者,然后对依赖的候选者进行注册实例过程,最终在
AbstractBeanFactory类的
getObjectForBeanInstance方法中通过一系列判断后调用父类
FactoryBeanRegistrySupport中的
getObjectFromFactoryBean方法-->
doGetObjectFromFactoryBean方法,调用如下(其中factory即为
FeignClientFactoryBean实例),
第四阶段
此处终于回到上面提到的问题,
getObject方法是何时被调用的呢?其是在注册相关调用bean实例后,分析依赖bean实例,然后至cache中查找是否已经完成实例化,如果没有实例化则继续通过
getBean()方法完成其对应的实例化过程,此处是通过FactoryBean方式获取实例,继续来看看
FeignClientFactoryBean中是如何实现的呢?
终于看到最开始我们注册实例化的
Feign.Builder实例,
feign方法定义如下
红色框中的内容来自于
@FeignClient注解中
configuration配置类中的定义,其定义在
FeignAutoConfiguration自动装配类中
而
FeignClientSpecification正是来源于
FeignClientsRegistrar中的
registerDefaultConfiguration(metadata, registry);方法实现,继续来看看
loadBalance方法的定义
此时的
target实例即为我们最初装配实例化的
HystrixTargeter,
target方法实现如下
- 第一个红色框很好理解,强转为我们的HystrixFeign.Builder实例;
- 第二个红色框可能看着比较默认,我们来看下其又是如何定义的:
实现了一个默认的
Default内部类,通过实现的
create方法可以看到,其主要是定义了
HystrixCommand的
Setter参数。这里我们可以再理解下
FeignContext的作用,其协助我们实现了多个
FeignClient的
config配置隔离,故类似的
SetterFactory对应的bean实例均会配置在
@FeignClient的
configuration参数对应的配置类中,从而实现隔离。如果没有对应的配置将采用默认值,默认值设定在
HystrixFeign类中
- 通过第三个和第四个红色框可以看到,其定义了fallback的实现优先级,如果没有定义对应的fallback则直接进入默认实现。
我们来看看
fallbackFactory对应的target实现
如上在有
fallback定义下,通过设定动态代理实现集成
Hystrix应用,
可以看到上述配置定义的
SetterFactory在此处通过构造函数传入
HystrixInvocationHandler实例中,
HystrixInvocationHandler是JDK动态代理的实现,通过切面技术对待执行方法实现
HystrixCommand的应用,重写实现
fallback功能,
无论是否定义fallback,均会执行
父类的
build,
如果没有fallback定义则
invocationHandlerFactory采用默认定义,
真正的返回值在
ReflectiveFeign中的
newInstance被返回,其值为我们定义的
FeignClient,继续来看看源码定义
- 第一个红色框methodToHandler正是后续传入invocationHandler中的重要参数,其定义了feignClient下所有符合条件的method,如下dispatch就是此处的methodToHandler参数,其对每个方法设定了对应的回退方法和setterFactory参数,用于实例化HystrixCommand以及调用fallback方法:
- 第二个红色框中根据一定条件判断遍历载入当前接口中定义的方法至methodToHandler中;
- 第三个红色框使用JDK动态代理构建了代理对象proxy;
如果我们定义了多个
FeignClient接口,其均会在
@Autowired注入时重复通过
FeignClientFactoryBean创建。
总结
通过上面的源码分析,可以看到,其核心源于spring ioc的实现原理。通过动态代理实现对方法的切面处理,从而集成Hystrix。
扩展
正如前言中描述,本章源码的跟踪的目的是为了在Feign中实现request cache。可以看到在
HystrixInvocationHandler的
invoke方法实现中,其通过定义
HystrixCommand完成Hystrix的集成
其实现了
run()方法执行核心业务,实现了
getFallback()方法实现降级回退。通过之前Hystrix中Cache的应用,我们知道此处需要重写
getCacheKey()方法即可启用Cache功能应用。
剧透:后续会专门写一章来介绍如何实现Feign中的request cache应用,部分源码参考如下:
- CacheInvocationContextFactory :注册了cache实现的相关action事件,如获取cachekey方法等
- CacheInvocationContext :将源方法参数进行了一定逻辑处理,便于后续cachekey生成时应用
- HystrixCacheKeyGenerator :提供了cachekey的生成