Feign源码解析:初始化过程(一)
前言
打算系統分析下Feign的代碼,上一篇講了下Feign的歷史,本篇的話,先講下Feign相關的beanDefinition,beanDefinition就是bean的設計圖,bean都是按照beanDefinition來制造的。
Feign相關的bean不少,有一些是因為我們的Feign相關注解而引入的,有一部分是因為spring的自動裝配來自動引入的。
今天講講因為我們注解引入的那些。
EnableFeignClients引入的FeignClientSpecification
如果項目用到Feign,在@SpringBootApplication注解的主類上,我們一般還會加上@EnableFeignClients注解。
package a.b.c;
@SpringBootApplication
@EnableFeignClients
public class DemoApplication
實際上,spring的beanFactory初始化一般就是分兩步,第一步,收集beanDefinition列表,第二步,根據beanDefinition生成并初始化bean。
收集beanDefinition相當重要,但是beanDefinition從哪來來呢,一開始的時候,都是框架默認注冊了幾個,應用自身的beanDefinition一般要從主類來,也就是上面說的@SpringBootApplication注解的主類。
一開始就會去解析主類上的注解,包名;解析包名的原因是,拿到包名后,默認就會去掃描這個包名下的class,再找到注解了@configuration、@controller、@service、@component之類的bean作為beanDefinition;解析注解的原因是,可以根據注解,引入更多的beanDefinition。
以@EnableFeignClients為例,該注解的定義如下:
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients
所以這里會通過Import引入更多的beanDefinition。
org.springframework.cloud.openfeign.FeignClientsRegistrar
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 1
registerDefaultConfiguration(metadata, registry);
// 2
registerFeignClients(metadata, registry);
}
上面的1處,如下:
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
String name = "default." + metadata.getClassName();
registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(builder.getBeanDefinition());
}
主要是獲取@EnableFeignClients這個注解相關的默認屬性,然后注冊了一個FeignClientSpecification類型的bean。
這個FeignClientSpecification類很簡單,屬性就兩個:
public class FeignClientSpecification implements NamedContextFactory.Specification {
private String name;
private Class<?>[] configuration;
}
一個name,一個configuration,其實就是代表了一個要如何創建和配置FeignClient的配置,包含了如何創建feign的encoder、decoder等。
A custom @Configuration for all feign clients. Can contain override @Bean definition for the pieces that make up the client, for instance feign.codec.Decoder, feign.codec.Encoder, feign.Contract.
我們舉個例子,如下的代碼,
package a.b.c;
@SpringBootApplication
@EnableFeignClients
public class DemoApplication
最終生成的FeignClientSpecification beanDefinition,beanName為:default.a.b.c.DemoApplication.FeignClientSpecification,屬性:
name:default.a.b.c.DemoApplication
configuration:空數組
FeignClient注解引入的FeignClientSpecification
說完這個,繼續說如下的二處:
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 1
registerDefaultConfiguration(metadata, registry);
// 2
registerFeignClients(metadata, registry);
}
2處內部,是首先獲取@EnableFeignClients注解的類所在的包名,然后在這個包名下掃描注解了@FeignClient的類,掃描到的這些類,那肯定是弄了一個beanDefinition了,這個可以倒推,畢竟每個@FeignClient注解的類最終都變成了一個bean嘛。
里面有一點值得講,FeignClient注解中有一個屬性,叫configuration,它支持你指定一個class,里面可以覆蓋FeignClient內部的部分組件,如Feign的Encoder等
public @interface FeignClient {
String value() default "";
Class<?>[] configuration() default {};
}
這個你指定的配置類,是可以被多個FeignClient復用的,所以,spring內部也是只會存一份,這一份配置,就通過一個創建一個類型為FeignClientSpecification.class的bean來保存。
以如下為例:
@FeignClient(name = "demoService")
public interface DemoServiceFeignClient
由于沒有指定configuration屬性,這里生成的FeignClientSpecification bean中,name就是demoService,configuration就是null:
public class FeignClientSpecification implements NamedContextFactory.Specification {
private String name;
private Class<?>[] configuration;
}
如果指定configuration屬性:
@FeignClient(name = "demoService", configuration = CustomConfig.class)
public interface DemoServiceFeignClient
FeignClientSpecification中的configuration就會是CustomConfig.class。
這個FeignClientSpecification.class的bean的名字,默認則會是demoService.FeignClientSpecification。
FeignClient對應的beanDefinition
接下來,就是注冊一個FeignClient對應的beanDefinition了。
詳細的源碼可以看這里:org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient
FeignClient的bean是較難創建的,所以這里是用了工廠bean:FeignClientFactoryBean
String name = getName(attributes);
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
factoryBean.setRefreshableClient(isClientRefreshEnabled());
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
...
return factoryBean.getObject();
});
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
這里的beanname,一般也就是FeignClient的全路徑名。
匯總
最終其實就引入了三個bean:
首先是由@enableFeignClients引入的FeignClientSpecification,然后是@enableFeignClients注解所在的包名下的由@FeignClient注解引入的FeignClientSpecification,再一個就是FeignClient本身的bean(一個factoryBean)
總結
以上是生活随笔為你收集整理的Feign源码解析:初始化过程(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 这些iPad所独占的操作手势你都需要学会
- 下一篇: iPhone进水以后第一时间应该怎么做