Feign源碼解析:初始化過程(三)

背景

前面兩篇講了下,在一個典型的引入了feign、loadbalancer、nacos等相關依賴的環境中,會有哪些bean需要創建。

其中第一篇講了非自動配置的bean,第二篇是自動配置的bean。第一篇中提到,@FeignClient這個註解,就會創建一個beanDefinition,類型爲FeignClientFactoryBean,是一個工廠bean,就是用它來創建一個FeignClient。

public class FeignClientFactoryBean
		implements FactoryBean<Object>

下面就來看看這個FeignClient是如何創建出來的。

創建過程

這個工廠bean裏包含的屬性,都是用來創建FeignClient的,它的字段,基本和@FeignClient這個註解裏的字段差不多。

private Class<?> type;

private String name;

private String url;

private String contextId;

private String path;

private Class<?> fallback = void.class;

private Class<?> fallbackFactory = void.class;

private int readTimeoutMillis = new Request.Options().readTimeoutMillis();

private int connectTimeoutMillis = new Request.Options().connectTimeoutMillis();

private boolean followRedirects = new Request.Options().isFollowRedirects();

創建bean的代碼如下:

image-20231229165611822

我們走到上面紅框處後,factory的屬性如下:

image-20231229165806435

debug進入getObject方法後:

public Object getObject() {
   return getTarget();
}

然後需要獲取一個FeignContext類型的bean:

<T> T getTarget() {
    FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
        : applicationContext.getBean(FeignContext.class);

這是從spring容器中獲取FeignContext,那麼,這個bean是在哪裏註冊的呢?

是在如下的自動裝配類中:

org.springframework.cloud.openfeign.FeignAutoConfiguration#feignContext

@Bean
public FeignContext feignContext() {
   FeignContext context = new FeignContext();
   context.setConfigurations(this.configurations);
   return context;
}

FeignContext

該類繼承如下,它繼承了的類叫做NamedContextFactory,根據名字猜測,這是個工廠類,生產什麼東西呢,

是NamedContext,也就是說,命名spring容器上下文,下面我們就知道,這個類會給每個FeignClient創建一個spring容器,各自獨立。

public class FeignContext extends NamedContextFactory<FeignClientSpecification>

public FeignContext() {
    super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}


public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>

這個類的有一個很重要的字段,因爲每一個FeignClient最終都會創建一個spring容器,這裏就是一個map,key就是FeignClient的名稱,value就是對應的spring容器。

private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

但是此時,還不會去創建各個FeignClient的spring容器,只是將各個FeignClient的配置保存起來:

image-20231229171705011

創建對應的Feign spring容器

回到之前的如下代碼,拿到FeignContext這個bean之後,要做啥呢:

<T> T getTarget() {
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
// 2
Feign.Builder builder = feign(context);

繼續上圖的2處:

protected Feign.Builder feign(FeignContext context) {
    // 2.1 
    FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
    Logger logger = loggerFactory.create(type);

    // 2.2
    Feign.Builder builder = get(context, Feign.Builder.class)
        .logger(logger)
        .encoder(get(context, Encoder.class))
        .decoder(get(context, Decoder.class))
        .contract(get(context, Contract.class));
	// 2.3
    configureFeign(context, builder);

    return builder;
}

2.1處,從FeignContext獲取FeignLoggerFactory這個類型的bean,但此時,該FeignClient的spring容器其實還沒創建呢:

image-20231229172219659

此時,我們得先創建對應的spring容器(此處是懶加載模式):

image-20231229172318349

創建代碼就是上圖中的:

this.contexts.put(name, createContext(name));

這個createContext方法,說白了,也就是下面的內容:

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(beanFactory);

容器創建好了,要往裏面放什麼beanDefinition呢?

首先,就是這個FeignClient註解中configuration字段指定的class註冊爲bean:

public @interface FeignClient {
...    
Class<?>[] configuration() default {};
if (this.configurations.containsKey(name)) {
    for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
        context.register(configuration);
    }
}

怎麼理解呢,比如,如下代碼,就會將A這個類註冊爲bean,且只是註冊到echo-service-provider對應的這個spring容器裏:

@FeignClient(value = "echo-service-provider",configuration = A.class) // 指向服務提供者應用
public interface EchoService {

在我們的代碼中,實際沒配置configuration,所以不會註冊:

image-20231229173249192

ok,除了這些各個FeignClient中指定的配置類,大家知道,其實我們@enableFeignClients註解,其實也是可以配置這個屬性的,這種是配置各個FeignClient都能用的默認配置:

public @interface EnableFeignClients {
	Class<?>[] defaultConfiguration() default {};
}

所以,接下來還會檢查有沒有默認配置:

for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
   if (entry.getKey().startsWith("default.")) {
      for (Class<?> configuration : entry.getValue().getConfiguration()) {
         context.register(configuration);
      }
   }
}

我們這邊也沒有配置(在@enableFeignClients中沒配置defaultConfiguration字段),所以也不會註冊任何bean。

但,Feign默認就會配置一堆encoder、decoder等bean,這些配置是怎麼來的呢?

答案就在如下的構造函數中,可以看到,super調用的第一個參數是個class,FeignClientsConfiguration.class,它就是我們的默認配置類。

public FeignContext() {
    super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}

裏面包含了各種自動配置:

image-20240107145432892

這裏簡單列舉:

feign.codec.Decoder、feign.codec.Encoder、feign.Contract、feign.Retryer、org.springframework.format.support.FormattingConversionService、org.springframework.cloud.openfeign.FeignLoggerFactory、org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer

配置FeignBuilder

既然該FeignClient對應的容器準備好了,接下來就是繼續創建FeignClient,創建它是通過FeignBuilder:

// 1
Feign.Builder builder = get(context, Feign.Builder.class)
    // required values
    .logger(logger)
    .encoder(get(context, Encoder.class))
    .decoder(get(context, Decoder.class))
    .contract(get(context, Contract.class));
// 2
configureFeign(context, builder);

創建FeignBuilder也是直接從容器獲取,然後配置其logger、encoder、decoder、contract。

這幾個大件現在配置好了,接下來開始配置其他東西,也就是上面的2處。跟進如下:

image-20240107151005183

這裏首先是可以根據用戶的配置來設置一些屬性:

@ConfigurationProperties("feign.client")
public class FeignClientProperties {
    
	private boolean defaultToProperties = true;

	private String defaultConfig = "default";

	private Map<String, FeignClientConfiguration> config = new HashMap<>();

	private boolean decodeSlash = true;
}

其次,也可以根據FeignClientConfigurer這個bean來配置部分屬性,默認情況下,獲取到的這個bean是空的:

@Bean
@ConditionalOnMissingBean(FeignClientConfigurer.class)
public FeignClientConfigurer feignClientConfigurer() {
    return new FeignClientConfigurer() {
        // 沒有重寫任何方法
    };
}

說白了,上圖這個方法,要麼根據你的properties配置來設置FeignBuilder,要麼根據FeignClientConfigurer。

根據url決定FeignClient的類型

完成了FeignBuilder的創建後,來到關鍵一環:

image-20240107152205171

根據url判斷是否爲空,來決定走哪個路徑。如果你url寫死了,那就自然是以你爲準,不需要去什麼服務發現中獲取服務實例列表,再用負載均衡來決定走哪個實例;如果url爲空,默認認爲是把你在FeignClient中指定的名字,認爲是服務的名稱,就要走服務發現機制+負載均衡機制了。

一般來說,微服務都是走服務發現機制。咱們這裏也是如此。

此時,在進入上圖的loadBalance方法前,我這裏url最終爲:http://echo-service-provider

loadBalance方法

接下來,開始跟蹤loadBalance方法:

image-20240107152812809

上圖紅框,需要從當前FeignClient對應的容器中獲取類型爲feign.Client的bean,而結合上文,我們知道,我們那個容器中,好像沒有這個類型的bean,那還能獲取到嗎?實際是可以的。

是從父容器獲取,父容器就是spring boot啓動時,默認的那個大的容器,裏面一般包含了我們的業務bean的,加上框架的bean,經常有大幾百個bean。

image-20240107154106441

這個bean的定義在哪裏呢,如何引入的呢?

image-20240107154533816

可以看到,這個bean是在DefaultFeignLoadBalancerConfiguration類中,這個類是在另一個自動配置類中引入的:

image-20240107154708359

這個自動裝配類上有一些condition,如:

@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })

這其中,LoadBalancerClientFactory來自於依賴:

image-20240107154901095

你要是沒加入spring-loadbalancer的依賴,你自然也就不會激活這個自動裝配類,也就不會有feign.Client這個bean,程序也就起不來。

繼續看之前的feignClient的bean,是採用了構造器注入:

image-20240107154533816

注入了LoadBalancerClient和LoadBalancerClientFactory這兩個bean。

這兩個bean是在哪裏定義的呢?

我發現一個好辦法來找bean定義的地方,根據method return type來找,看看哪裏返回這個type:

image-20240107161653116

發現是在如下自動裝配類,這個類是在loadbalancer的相關依賴中:

image-20240107161801109

image-20240107162144630

而這個bean又依賴構造器中的參數,LoadBalancerClientFactory,同樣的方式找到它:

image-20240107162256310

它則依賴瞭如下bean,這是個配置屬性類:

@ConfigurationProperties("spring.cloud.loadbalancer")
public class LoadBalancerClientsProperties extends LoadBalancerProperties {

通過以上這些步驟,可以說,FeignClient基本就創建好了,最終就是如下紅框的幾個步驟:

image-20240107164130568

最終就是做了些對象封裝:

image-20240107164257163

創建動態代理對象給業務側調用:

image-20240107164625447

基本的流程就這些,這塊的分析就沒有太細了,各個FeignClient對應的對象創建完成後,程序也就完成了啓動,啓動後,feign調用的流程,尤其是loadbalancer部分,是怎麼工作的呢,又有哪些坑呢,下篇繼續講講。

總結

發現寫源碼是真的枯燥,本來是因爲想把某個問題講清楚,但不結合源碼,又講不清楚,沒轍。

上一篇還是2023年,這篇跨年了,2024年,大家新年快樂。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章