Feign 註冊到容器
和 springboot 自動配置原理類似,在 springboot 啟動時會掃描到 EnableFeignClients 註解,這個註解匯入了一個 FeignClientsRegistrar 類
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {}
FeignClientsRegistrar 實現了 ImportBeanDefinitionRegistrar,所以會透過 registerBeanDefinitions 方法注入一批 BeanDefinition
// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerBeanDefinitions
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 註冊配置類(上一篇文章說過的 EnableFeignClients.defaultConfiguration 屬性)
registerDefaultConfiguration(metadata, registry);
// 註冊 feign 客戶端
registerFeignClients(metadata, registry);
}
// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClients
// registerFeignClients,引數應該不陌生了,一個是元註解資訊,一個是 ImportBeanDefinitionRegistrar 用來註冊 bean 的
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
// 透過元註解獲取到註解屬性(就是 EnableFeignClients 的配置項)
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
// 先看有沒有配 clients
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
// 掃描使用了 FeignClient 註解的類
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
// clients 指定的類
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
...
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
實際註冊 bean 的方法 registerFeignClient,裡面根據配置資訊構建了一個 BeanDefinition,構建的過程還是比較簡單就是設定 beanName、type、lazy 等基礎資訊和 EnableFeignClients 配置的 fallback 等
// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
// 是否延遲載入,讀取的配置項,true:使用 feign 時才建立;false(預設):專案啟動時建立
if (String.valueOf(false).equals(this.environment.getProperty("spring.cloud.openfeign.lazy-attributes-resolution", String.valueOf(false)))) {
this.eagerlyRegisterFeignClientBeanDefinition(className, attributes, registry);
} else {
this.lazilyRegisterFeignClientBeanDefinition(className, attributes, registry);
}
}
// org.springframework.cloud.openfeign.FeignClientsRegistrar#eagerlyRegisterFeignClientBeanDefinition
private void eagerlyRegisterFeignClientBeanDefinition(String className, Map<String, Object> attributes, BeanDefinitionRegistry registry) {
this.validate(attributes);
// 構建一個 BeanDefinitionBuilder(genericBeanDefinition 方法把 bealClass 設定為 FeignClientFactoryBean)
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
definition.addPropertyValue("url", this.getUrl((ConfigurableBeanFactory)null, attributes));
definition.addPropertyValue("path", this.getPath((ConfigurableBeanFactory)null, attributes));
String name = this.getName(attributes);
definition.addPropertyValue("name", name);
String contextId = this.getContextId((ConfigurableBeanFactory)null, attributes);
definition.addPropertyValue("contextId", contextId);
// 省略了 BeanDefinitionBuilder 的賦值操作
...
// 根據 beanDefinition 再構建一個 BeanDefinitionHolder
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
// 關聯 BeanDefinitionHolder 和 registry(registry 持有 BeanDefinitionHolder,後續透過 registry 建立物件)
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
// 方法呼叫了兩次,但是引數不一樣,一個是建立 Feign 的配置項(Request.Options,超時配置),一個是 Feign
this.registerRefreshableBeanDefinition(registry, contextId, Request.Options.class, OptionsFactoryBean.class);
this.registerRefreshableBeanDefinition(registry, contextId, RefreshableUrl.class, RefreshableUrlFactoryBean.class);
}
// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerRefreshableBeanDefinition
private void registerRefreshableBeanDefinition(BeanDefinitionRegistry registry, String contextId, Class<?> beanType,
Class<?> factoryBeanType) {
if (isClientRefreshEnabled()) {
String beanName = beanType.getCanonicalName() + "-" + contextId;
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(factoryBeanType);
definitionBuilder.setScope("refresh");
definitionBuilder.addPropertyValue("contextId", contextId);
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(definitionBuilder.getBeanDefinition(),
beanName);
// 反射建立物件(前面說了 beanClass 是 FactoryBean)
definitionHolder = ScopedProxyUtils.createScopedProxy(definitionHolder, registry, true);
// 註冊 beanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
}
FactoryBean 這個在前面的文章有說過,這是 spring 的知識,工廠方式構建 bean 的方式:1,使用靜態方法;2:使用例項方法;3:使用 FactoryBean。如果是 FactoryBean,最終的物件是 getObject 方法返回的物件作為 bean,最終的代理物件怎麼生成的就不貼原始碼了,程式碼量也不多,看下最終的代理物件長啥樣吧
Feign 呼叫流程
就是把容器中 Feign 的代理物件取出來,然後反射執行方法的過程,先對比下剛才放到單例池的代理物件和注入的物件
先判斷是否是 Object 的方法,然後執行代理物件的方法,最後傳送一個 http 請求,呼叫鏈如下