前言
打算系統分析下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)