Feign原始碼解析:初始化過程(一)

三國夢迴發表於2023-12-16

前言

打算系統分析下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:

image-20231216211043164

首先是由@enableFeignClients引入的FeignClientSpecification,然後是@enableFeignClients註解所在的包名下的由@FeignClient註解引入的FeignClientSpecification,再一個就是FeignClient本身的bean(一個factoryBean)

相關文章