Feign源码解析:初始化过程(三)
背景
前面兩篇講了下,在一個(gè)典型的引入了feign、loadbalancer、nacos等相關(guān)依賴的環(huán)境中,會(huì)有哪些bean需要?jiǎng)?chuàng)建。
其中第一篇講了非自動(dòng)配置的bean,第二篇是自動(dòng)配置的bean。第一篇中提到,@FeignClient這個(gè)注解,就會(huì)創(chuàng)建一個(gè)beanDefinition,類型為FeignClientFactoryBean,是一個(gè)工廠bean,就是用它來(lái)創(chuàng)建一個(gè)FeignClient。
public class FeignClientFactoryBean
implements FactoryBean<Object>
下面就來(lái)看看這個(gè)FeignClient是如何創(chuàng)建出來(lái)的。
創(chuàng)建過(guò)程
這個(gè)工廠bean里包含的屬性,都是用來(lái)創(chuàng)建FeignClient的,它的字段,基本和@FeignClient這個(gè)注解里的字段差不多。
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();
創(chuàng)建bean的代碼如下:
我們走到上面紅框處后,factory的屬性如下:
debug進(jìn)入getObject方法后:
public Object getObject() {
return getTarget();
}
然后需要獲取一個(gè)FeignContext類型的bean:
<T> T getTarget() {
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
這是從spring容器中獲取FeignContext,那么,這個(gè)bean是在哪里注冊(cè)的呢?
是在如下的自動(dòng)裝配類中:
org.springframework.cloud.openfeign.FeignAutoConfiguration#feignContext
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
FeignContext
該類繼承如下,它繼承了的類叫做NamedContextFactory,根據(jù)名字猜測(cè),這是個(gè)工廠類,生產(chǎn)什么東西呢,
是NamedContext,也就是說(shuō),命名spring容器上下文,下面我們就知道,這個(gè)類會(huì)給每個(gè)FeignClient創(chuàng)建一個(gè)spring容器,各自獨(dú)立。
public class FeignContext extends NamedContextFactory<FeignClientSpecification>
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
這個(gè)類的有一個(gè)很重要的字段,因?yàn)槊恳粋€(gè)FeignClient最終都會(huì)創(chuàng)建一個(gè)spring容器,這里就是一個(gè)map,key就是FeignClient的名稱,value就是對(duì)應(yīng)的spring容器。
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
但是此時(shí),還不會(huì)去創(chuàng)建各個(gè)FeignClient的spring容器,只是將各個(gè)FeignClient的配置保存起來(lái):
創(chuàng)建對(duì)應(yīng)的Feign spring容器
回到之前的如下代碼,拿到FeignContext這個(gè)bean之后,要做啥呢:
<T> T getTarget() {
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
// 2
Feign.Builder builder = feign(context);
繼續(xù)上圖的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這個(gè)類型的bean,但此時(shí),該FeignClient的spring容器其實(shí)還沒(méi)創(chuàng)建呢:
此時(shí),我們得先創(chuàng)建對(duì)應(yīng)的spring容器(此處是懶加載模式):
創(chuàng)建代碼就是上圖中的:
this.contexts.put(name, createContext(name));
這個(gè)createContext方法,說(shuō)白了,也就是下面的內(nèi)容:
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(beanFactory);
容器創(chuàng)建好了,要往里面放什么beanDefinition呢?
首先,就是這個(gè)FeignClient注解中configuration字段指定的class注冊(cè)為bean:
public @interface FeignClient {
...
Class<?>[] configuration() default {};
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
context.register(configuration);
}
}
怎么理解呢,比如,如下代碼,就會(huì)將A這個(gè)類注冊(cè)為bean,且只是注冊(cè)到echo-service-provider對(duì)應(yīng)的這個(gè)spring容器里:
@FeignClient(value = "echo-service-provider",configuration = A.class) // 指向服務(wù)提供者應(yīng)用
public interface EchoService {
在我們的代碼中,實(shí)際沒(méi)配置configuration,所以不會(huì)注冊(cè):
ok,除了這些各個(gè)FeignClient中指定的配置類,大家知道,其實(shí)我們@enableFeignClients注解,其實(shí)也是可以配置這個(gè)屬性的,這種是配置各個(gè)FeignClient都能用的默認(rèn)配置:
public @interface EnableFeignClients {
Class<?>[] defaultConfiguration() default {};
}
所以,接下來(lái)還會(huì)檢查有沒(méi)有默認(rèn)配置:
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
我們這邊也沒(méi)有配置(在@enableFeignClients中沒(méi)配置defaultConfiguration字段),所以也不會(huì)注冊(cè)任何bean。
但,F(xiàn)eign默認(rèn)就會(huì)配置一堆encoder、decoder等bean,這些配置是怎么來(lái)的呢?
答案就在如下的構(gòu)造函數(shù)中,可以看到,super調(diào)用的第一個(gè)參數(shù)是個(gè)class,F(xiàn)eignClientsConfiguration.class,它就是我們的默認(rèn)配置類。
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
里面包含了各種自動(dòng)配置:
這里簡(jiǎn)單列舉:
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對(duì)應(yīng)的容器準(zhǔn)備好了,接下來(lái)就是繼續(xù)創(chuàng)建FeignClient,創(chuàng)建它是通過(guò)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);
創(chuàng)建FeignBuilder也是直接從容器獲取,然后配置其logger、encoder、decoder、contract。
這幾個(gè)大件現(xiàn)在配置好了,接下來(lái)開(kāi)始配置其他東西,也就是上面的2處。跟進(jìn)如下:
這里首先是可以根據(jù)用戶的配置來(lái)設(shè)置一些屬性:
@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;
}
其次,也可以根據(jù)FeignClientConfigurer這個(gè)bean來(lái)配置部分屬性,默認(rèn)情況下,獲取到的這個(gè)bean是空的:
@Bean
@ConditionalOnMissingBean(FeignClientConfigurer.class)
public FeignClientConfigurer feignClientConfigurer() {
return new FeignClientConfigurer() {
// 沒(méi)有重寫(xiě)任何方法
};
}
說(shuō)白了,上圖這個(gè)方法,要么根據(jù)你的properties配置來(lái)設(shè)置FeignBuilder,要么根據(jù)FeignClientConfigurer。
根據(jù)url決定FeignClient的類型
完成了FeignBuilder的創(chuàng)建后,來(lái)到關(guān)鍵一環(huán):
根據(jù)url判斷是否為空,來(lái)決定走哪個(gè)路徑。如果你url寫(xiě)死了,那就自然是以你為準(zhǔn),不需要去什么服務(wù)發(fā)現(xiàn)中獲取服務(wù)實(shí)例列表,再用負(fù)載均衡來(lái)決定走哪個(gè)實(shí)例;如果url為空,默認(rèn)認(rèn)為是把你在FeignClient中指定的名字,認(rèn)為是服務(wù)的名稱,就要走服務(wù)發(fā)現(xiàn)機(jī)制+負(fù)載均衡機(jī)制了。
一般來(lái)說(shuō),微服務(wù)都是走服務(wù)發(fā)現(xiàn)機(jī)制。咱們這里也是如此。
此時(shí),在進(jìn)入上圖的loadBalance方法前,我這里url最終為:http://echo-service-provider。
loadBalance方法
接下來(lái),開(kāi)始跟蹤loadBalance方法:
上圖紅框,需要從當(dāng)前FeignClient對(duì)應(yīng)的容器中獲取類型為feign.Client的bean,而結(jié)合上文,我們知道,我們那個(gè)容器中,好像沒(méi)有這個(gè)類型的bean,那還能獲取到嗎?實(shí)際是可以的。
是從父容器獲取,父容器就是spring boot啟動(dòng)時(shí),默認(rèn)的那個(gè)大的容器,里面一般包含了我們的業(yè)務(wù)bean的,加上框架的bean,經(jīng)常有大幾百個(gè)bean。
這個(gè)bean的定義在哪里呢,如何引入的呢?
可以看到,這個(gè)bean是在DefaultFeignLoadBalancerConfiguration類中,這個(gè)類是在另一個(gè)自動(dòng)配置類中引入的:
這個(gè)自動(dòng)裝配類上有一些condition,如:
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
這其中,LoadBalancerClientFactory來(lái)自于依賴:
你要是沒(méi)加入spring-loadbalancer的依賴,你自然也就不會(huì)激活這個(gè)自動(dòng)裝配類,也就不會(huì)有feign.Client這個(gè)bean,程序也就起不來(lái)。
繼續(xù)看之前的feignClient的bean,是采用了構(gòu)造器注入:
注入了LoadBalancerClient和LoadBalancerClientFactory這兩個(gè)bean。
這兩個(gè)bean是在哪里定義的呢?
我發(fā)現(xiàn)一個(gè)好辦法來(lái)找bean定義的地方,根據(jù)method return type來(lái)找,看看哪里返回這個(gè)type:
發(fā)現(xiàn)是在如下自動(dòng)裝配類,這個(gè)類是在loadbalancer的相關(guān)依賴中:
而這個(gè)bean又依賴構(gòu)造器中的參數(shù),LoadBalancerClientFactory,同樣的方式找到它:
它則依賴了如下bean,這是個(gè)配置屬性類:
@ConfigurationProperties("spring.cloud.loadbalancer")
public class LoadBalancerClientsProperties extends LoadBalancerProperties {
通過(guò)以上這些步驟,可以說(shuō),F(xiàn)eignClient基本就創(chuàng)建好了,最終就是如下紅框的幾個(gè)步驟:
最終就是做了些對(duì)象封裝:
創(chuàng)建動(dòng)態(tài)代理對(duì)象給業(yè)務(wù)側(cè)調(diào)用:
基本的流程就這些,這塊的分析就沒(méi)有太細(xì)了,各個(gè)FeignClient對(duì)應(yīng)的對(duì)象創(chuàng)建完成后,程序也就完成了啟動(dòng),啟動(dòng)后,feign調(diào)用的流程,尤其是loadbalancer部分,是怎么工作的呢,又有哪些坑呢,下篇繼續(xù)講講。
總結(jié)
發(fā)現(xiàn)寫(xiě)源碼是真的枯燥,本來(lái)是因?yàn)橄氚涯硞€(gè)問(wèn)題講清楚,但不結(jié)合源碼,又講不清楚,沒(méi)轍。
上一篇還是2023年,這篇跨年了,2024年,大家新年快樂(lè)。
總結(jié)
以上是生活随笔為你收集整理的Feign源码解析:初始化过程(三)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 不止八股:阿里内部语雀一些有趣的并发编程
- 下一篇: kafka源码阅读之MacBook Pr