0 依赖引入
1 | <dependencyManagement> |
首先来分析一下pom中如何引入feign的依赖包。如上所示,我这边使用的是spring-cloud的Greenwich.SR2
版本,通过spring-cloud-starter-openfeign
引入feign,后续分析也基于该版本进行。
那么有必要来看一下spring-cloud-starter-openfeign.pom
的定义
1 | <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
根据springboot-starter-*
的加载原理(可以参考我之前的文章《Springboot-starter-xxx原理解析》)我们知道springboot加载的是引入包下META-INF/spring.factories
文件,我们这里对应的就是spring-cloud-openfeign-core
包,来看看其spring.factories
的内容。
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
这里的FeignAcceptGzipEncodingAutoConfiguration
和FeignContentGzipEncodingAutoConfiguration
用于请求和响应的压缩暂时不做说明,我们来看看FeignRibbonClientAutoConfiguration
和FeignAutoConfiguration
都做了些什么。
1.1 FeignRibbonClientAutoConfiguration
1 | @ConditionalOnClass({ ILoadBalancer.class, Feign.class }) |
FeignHttpClientProperties
定义了http请求的默认参数,包括最大连接数、连接超时时间等。
通过@AutoConfigureBefore
注解我们可以看到该类的初始化要求是在FeignAutoConfiguration
之前的。
通过@Import
注解引入了HttpClientFeignLoadBalancedConfiguration
、OkHttpFeignLoadBalancedConfiguration
和DefaultFeignLoadBalancedConfiguration
几个配置类,但前两个类是包含条件注解@ConditionalOnClass
的,对应条件分别是ApacheHttpClient.class
和OkHttpClient.class
,也就是当选择http实现为ApacheHttpClient和OkHttpClient时才会触发初始化,当我们选择默认的http实现时,仅会触发DefaultFeignLoadBalancedConfiguration
的初始化,那么来看一下该类:
1 | @Configuration |
这里实例化了feignClient
为一个LoadBalancerFeignClient
类型的对象。
最后看一下cachingLBClientFactory
和retryabeCachingLBClientFactory
的初始化,根据@ConditionalOnMissingClass
和@ConditionalOnClass
值均为org.springframework.retry.support.RetryTemplate
可以看出这两个类是互斥的,根据RetryTemplate
类的存在与否仅会有一个bean被实例化。RetryTemplate
是spring5+版本增加的功能,需要另外引入spring-retry
包。
1.2 FeignAutoConfiguration
1 |
|
接着来看FeignAutoConfiguration
类,这里牵涉到4个内部类的初始化,其中HttpClientFeignConfiguration
和OkHttpFeignConfiguration
对应需要ApacheHttpClient.class
和OkHttpClient.class
类的存在,因此在默认情况下并不会执行初始化,暂不做分析。接着看一下HystrixFeignTargeterConfiguration
和DefaultFeignTargeterConfiguration
类,同样是根据feign.hystrix.HystrixFeign
类存在与否,二选一进行初始化。HystrixFeign
这个类在jar包feign.hystrix
下,而spring-cloud-starter-openfeign.pom
中正好有引入该包,那么这里很自然的就会选择初始化HystrixFeignTargeterConfiguration
类,同时初始化feignTargeter
为一个HystrixTargeter
类型的对象。
2 FeignClientsConfiguration
除了上述加载的类以外,spring-cloud-openfeign-core
包下还有一个类也会自动被加载,它就是FeignClientsConfiguration
,先来看一下其定义:
1 | @Configuration |
可以看到,由于@Configuration
注解的缘故,该类也会在初始化时被Spring加载,同时其内部类HystrixFeignConfiguration
也会被加载,只要引入了对应的类HystrixCommand
和HystrixFeign
,而这两个类分属于com.netflix.hystrix
和feign.hystrix
包,这在之前的spring-cloud-starter-openfeign.pom
中都有引入。另外需要注意的是feign.hystrix.enabled
这个配置,只有当显式设置为true
时,才会触发feignHystrixBuilder()
的初始化。另外需要注意的是feignBuilder
方法,当feign.hystrix.enabled
不为true
时,Feign.Builder
会由该方法来初始化。
那么来具体看一下HystrixFeign.builder()
做了什么。
1 | public static Builder builder() { |
这里的Builder
是HystrixFeign
类的内部类,继承自Feign.Builder
。
3 启动注解
1 | @Slf4j |
除了引入feign依赖之外,启动类上还需要添加注解@EnableFeignClients
,那么我们还得来看看EnableFeignClients
的实现:
1 | @Retention(RetentionPolicy.RUNTIME) |
这里要关注的是@Import(FeignClientsRegistrar.class)
,将FeignClientsRegistrar
注册为了bean,我们来看看该类的定义
1 | class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { |
该类实现了spring的3个接口ImportBeanDefinitionRegistrar
、ResourceLoaderAware
和EnvironmentAware
,根据spring的实现机制,该类初始化时会调用对应的方法实现。其中setResourceLoader
和setEnvironment
方法实现较为简单,重点需要关注的是registerBeanDefinitions
方法,该方法的入参AnnotationMetadata
为我们的启动类HuiMallOrderApplication
上的注解对应信息,BeanDefinitionRegistry
为所有bean的注册相关信息。
接着分别来看一下registerDefaultConfiguration
和registerFeignClients
方法的实现:
3.1 registerDefaultConfiguration
1 | private void registerDefaultConfiguration(AnnotationMetadata metadata, |
metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true)
方法返回EnableFeignClients
类的属性列表,包括了defaultConfiguration
属性,因此会进入执行if分支的内容。接着来看一下registerClientConfiguration
方法:
1 | private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, |
该类主要做的事情是将@EnableFeignClients
中自定义的defaultConfiguration
值注册到registry
中,这里注册的beanName为default+类全名,而bean定义为FeignClientSpecification
的构造函数参数中增加对应的name
和configure
值。
3.2 registerFeignClients
1 | public void registerFeignClients(AnnotationMetadata metadata, |
这里的ClassPathScanningCandidateComponentProvider
是Spring提供的工具,用于查找classpath下符合要求的class文件。该方法看着很长,其实做的事情就是初始化scanner
并扫描指定包路径下包含@FeignClient
注解的类。并将每一个类通过registerClientConfiguration
方法注册到registry
中。这里有两个方法需要关注一下,分别是getClientName(attributes)
和registerFeignClient(registry, annotationMetadata, attributes)
。
先看getClientName(attributes)
:
1 | private String getClientName(Map<String, Object> client) { |
该方法用于生成注册到registry
的name,可以看到getClientName
对name的取值优先级依次是contextId->value->name->serviceId,根据取得的name去注册registry
。
接着来看registerFeignClient(registry, annotationMetadata, attributes)
:
1 | private void registerFeignClient(BeanDefinitionRegistry registry, |
这个方法主要做的事情是从attributes
中将之前@FeignClient
注解上获取的属性都添加到attributes
对象上,根据attributes
对象生成BeanDefinitionHolder
对象,这里需要关注的是definition
对象的初始化是基于FeignClientFactoryBean
的,该类定义为:
1 | class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware |
因为实现了FactoryBean<Object>
接口的关系,所以该类实现了getObject()
方法,因此基于FeignClientFactoryBean
创建的bean对象的注入会依赖于该方法,此处暂不做深入分析。
BeanDefinitionHolder
对象创建完成后,最后调用了BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry)
方法完成对象的注册,来看看该方法:
1 | public static void registerBeanDefinition( |
registerBeanDefinition
方法之前已经见到过了,所以这里做的事情其实就是把生成的生成BeanDefinitionHolder
对象注册到registry
上,同时注册其对应的别名alias
。
到此feign相关的整个初始化工作也就算是完成了。
4 总结
本文梳理了feign在项目中的基本初始化流程,了解了feign中不同条件下初始化时产生的不同变化,但并未涉及基于feign进行接口调用时的源码分析,这会在后续的文章中补上。