背景
前面兩篇講了下,在一個典型的引入了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的代碼如下:
我們走到上面紅框處後,factory的屬性如下:
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的配置保存起來:
創建對應的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容器其實還沒創建呢:
此時,我們得先創建對應的spring容器(此處是懶加載模式):
創建代碼就是上圖中的:
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,所以不會註冊:
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");
}
裏面包含了各種自動配置:
這裏簡單列舉:
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處。跟進如下:
這裏首先是可以根據用戶的配置來設置一些屬性:
@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的創建後,來到關鍵一環:
根據url判斷是否爲空,來決定走哪個路徑。如果你url寫死了,那就自然是以你爲準,不需要去什麼服務發現中獲取服務實例列表,再用負載均衡來決定走哪個實例;如果url爲空,默認認爲是把你在FeignClient中指定的名字,認爲是服務的名稱,就要走服務發現機制+負載均衡機制了。
一般來說,微服務都是走服務發現機制。咱們這裏也是如此。
此時,在進入上圖的loadBalance方法前,我這裏url最終爲:http://echo-service-provider
。
loadBalance方法
接下來,開始跟蹤loadBalance方法:
上圖紅框,需要從當前FeignClient對應的容器中獲取類型爲feign.Client的bean,而結合上文,我們知道,我們那個容器中,好像沒有這個類型的bean,那還能獲取到嗎?實際是可以的。
是從父容器獲取,父容器就是spring boot啓動時,默認的那個大的容器,裏面一般包含了我們的業務bean的,加上框架的bean,經常有大幾百個bean。
這個bean的定義在哪裏呢,如何引入的呢?
可以看到,這個bean是在DefaultFeignLoadBalancerConfiguration類中,這個類是在另一個自動配置類中引入的:
這個自動裝配類上有一些condition,如:
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
這其中,LoadBalancerClientFactory來自於依賴:
你要是沒加入spring-loadbalancer的依賴,你自然也就不會激活這個自動裝配類,也就不會有feign.Client這個bean,程序也就起不來。
繼續看之前的feignClient的bean,是採用了構造器注入:
注入了LoadBalancerClient和LoadBalancerClientFactory這兩個bean。
這兩個bean是在哪裏定義的呢?
我發現一個好辦法來找bean定義的地方,根據method return type來找,看看哪裏返回這個type:
發現是在如下自動裝配類,這個類是在loadbalancer的相關依賴中:
而這個bean又依賴構造器中的參數,LoadBalancerClientFactory,同樣的方式找到它:
它則依賴瞭如下bean,這是個配置屬性類:
@ConfigurationProperties("spring.cloud.loadbalancer")
public class LoadBalancerClientsProperties extends LoadBalancerProperties {
通過以上這些步驟,可以說,FeignClient基本就創建好了,最終就是如下紅框的幾個步驟:
最終就是做了些對象封裝:
創建動態代理對象給業務側調用:
基本的流程就這些,這塊的分析就沒有太細了,各個FeignClient對應的對象創建完成後,程序也就完成了啓動,啓動後,feign調用的流程,尤其是loadbalancer部分,是怎麼工作的呢,又有哪些坑呢,下篇繼續講講。
總結
發現寫源碼是真的枯燥,本來是因爲想把某個問題講清楚,但不結合源碼,又講不清楚,沒轍。
上一篇還是2023年,這篇跨年了,2024年,大家新年快樂。