㈠ Spring自動裝配原理
其中它主要是依賴一個父項目,主要是管理項目的資源過濾及插件!
點進去,發現還有一個父依賴
這里才是真正管理SpringBoot應用裡面所有依賴版本的地方,SpringBoot的版本控制中心;
以後我們導入依賴默認是不需要寫版本;但是如果導入的包沒有在依賴中管理著就需要手動配置版本了;
springboot-boot-starter-xxx :就是spring-boot的場景啟動器
SpringBoot將所有的功能場景都抽取出來,做成一個個的starter (啟動器),只需要在項目中引入這些starter即可,所有相關的依賴都會導入進來 , 我們要用什麼功能就導入什麼樣的場景啟動器即可 ;我們未來也可以自己自定義 starter;
默認的主啟動類
但是一個簡單的啟動類並不簡單!我們來分析一下這些註解都幹了什麼
@SpringBootApplication
作用:標注在某個類上說明這個類是SpringBoot的主配置類 , SpringBoot就應該運行這個類的main方法來啟動SpringBoot應用;
進入這個註解:可以看到上面還有很多其他註解!
@ComponentScan
這個註解在Spring中很重要 ,它對應XML配置中的元素。
作用:自動掃描並載入符合條件的組件或者bean , 將這個bean定義載入到IOC容器中
@SpringBootConfiguration
作用:SpringBoot的配置類 ,標注在某個類上 , 表示這是一個SpringBoot的配置類;
我們繼續進去這個註解查看
這里的 @Configuration,說明這是一個配置類 ,配置類就是對應Spring的xml 配置文件;
裡面的 @Component 這就說明,啟動類本身也是Spring中的一個組件而已,負責啟動應用!
我們回到 SpringBootApplication 註解中繼續看。
@EnableAutoConfiguration :開啟自動配置功能
以前我們需要自己配置的東西,而現在SpringBoot可以自動幫我們配置 ;@EnableAutoConfiguration告訴SpringBoot開啟自動配置功能,這樣自動配置才能生效;
點進註解接續查看:
@AutoConfigurationPackage :自動配置包
@import :Spring底層註解@import , 給容器中導入一個組件
Registrar.class 作用:將主啟動類所在包及包下面所有子包裡面的所有組件掃描到Spring容器 ;
這個分析完了,退到上一步,繼續看
@Import({.class}) :給容器導入組件 ;
:自動配置導入選擇器,那麼它會導入哪些組件的選擇器呢?我們點擊去這個類看源碼:
1、這個類中有一個這樣的方法:
2、這個方法又調用了 SpringFactoriesLoader 類的靜態方法!我們進入SpringFactoriesLoader類loadFactoryNames() 方法
3、我們繼續點擊查看 loadSpringFactories 方法
4、發現一個多次出現的文件:spring.factories,全局搜索它
spring.factories
我們根據源頭打開spring.factories , 看到了很多自動配置的文件;這就是自動配置根源所在!
可以看到這些一個個的都是javaConfig配置類,而且都注入了一些Bean,可以找一些自己認識的類,看著熟悉一下!
所以,自動配置真正實現是從classpath中搜尋所有的META-INF/spring.factories配置文件 ,並將其中對應的 org.springframework.boot.autoconfigure. 包下的配置項,通過反射實例化為對應標注了 @Configuration的JavaConfig形式的IOC容器配置類 , 然後將這些都匯總成為一個實例並載入到IOC容器中。
結論:
不簡單的方法
我最初以為就是運行了一個main方法,沒想到卻開啟了一個服務;
SpringApplication.run分析
分析該方法主要分兩部分,一部分是SpringApplication的實例化,二是run方法的執行
springApplication這個類主要做了以下四件事情:
1、推斷應用的類型是普通的項目還是Web項目
2、查找並載入所有可用初始化器 , 設置到initializers屬性中
3、找出所有的應用程序監聽器,設置到listeners屬性中
4、推斷並設置main方法的定義類,找到運行的主類
run方法流程分析
㈡ SpringBoot自動裝配原理
初看@SpringBootApplication有很多的註解組成,其實歸納就是一個"三體"結構,重要的只有三個Annotation:
(1)@Configuration註解
(2)@ComponentScan
(3)@EnableAutoConfiguration
從源碼中可以知道,最關鍵的要屬@Import(.class),藉助,@EnableAutoConfiguration可以幫助SpringBoot應用將所有符合條件的@Configuration配置都載入到當前SpringBoot創建並使用的IoC容器。同時藉助於Spring框架原有的一個工具類:SpringFactoriesLoader,@EnableAutoConfiguration就可以實現智能的自動配置。
總結 :@EnableAutoConfiguration作用就是從classpath中搜尋所有的META-INF/spring.factories配置文件,並將其中org.springframework.boot.autoconfigure.EnableutoConfiguration對應的配置項通過反射(Java Refletion)實例化為對應的標注了@Configuration的JavaConfig形式的IoC容器配置類,然後匯總為一個並載入到IoC容器。這些功能配置類要生效的話,會去classpath中找是否有該類的依賴類(也就是pom.xml必須有對應功能的jar包才行)並且配置類裡面注入了默認屬性值類,功能類可以引用並賦默認值。生成功能類的原則是自定義優先,沒有自定義時才會使用自動裝配類。
1、從spring-boot-autoconfigure.jar/META-INF/spring.factories中獲取redis的相關配置類全限定名(有120多個的配置類)RedisAutoConfiguration,一般一個功能配置類圍繞該功能,負責管理創建多個相關的功能類,比如RedisAutoConfiguration負責:JedisConnectionFactory、RedisTemplate、StringRedisTemplate這3個功能類的創建
2、RedisAutoConfiguration配置類生效的一個條件是在classpath路徑下有RedisOperations類存在,因此springboot的自動裝配機制會會去classpath下去查找對應的class文件。
3.如果pom.xml有對應的jar包,就能匹配到對應依賴class,
4、匹配成功,這個功能配置類才會生效,同時會注入默認的屬性配置類@EnableConfigurationProperties(RedisProperties.class)
5.Redis功能配置裡面會根據條件生成最終的JedisConnectionFactory、RedisTemplate,並提供了默認的配置形式@ConditionalOnMissingBean(name = "redisTemplate")
6.最終創建好的默認裝配類,會通過功能配置類裡面的 @Bean註解,注入到IOC當中
7.用戶使用,當用戶在配置文件中自定義時候就會覆蓋默認的配置@ConditionalOnMissingBean(name = "redisTemplate")
1.通過各種註解實現了類與類之間的依賴關系,容器在啟動的時候Application.run,會調用.class的selectImports方法(其實是其父類的方法)-- 這里需要注意,調用這個方法之前發生了什麼和是在哪裡調用這個方法需要進一步的探討
2.selectImports方法最終會調用SpringFactoriesLoader.loadFactoryNames方法來獲取一個全面的常用BeanConfiguration列表
3.loadFactoryNames方法會讀取FACTORIES_RESOURCE_LOCATION(也就是spring-boot-autoconfigure.jar 下面的spring.factories),獲取到所有的Spring相關的Bean的全限定名ClassName,大概120多個
4.selectImports方法繼續調用filter(configurations, autoConfigurationMetadata);這個時候會根據這些BeanConfiguration裡面的條件,來一一篩選,最關鍵的是
@ConditionalOnClass,這個條件註解會去classpath下查找,jar包裡面是否有這個條件依賴類,所以必須有了相應的jar包,才有這些依賴類,才會生成IOC環境需要的一些默認配置Bean
5.最後把符合條件的BeanConfiguration注入默認的EnableConfigurationPropertie類裡面的屬性值,並且注入到IOC環境當中
㈢ 自動裝配解決了什麼問題
之前提到的@Componen, @Configurationt這些在單純的寫業務的時候需要用到的註解,不過僅限於在當前工程下面處理自己的業務類。
當自己需要寫一個框架的時候,必然會引入很多第三方包,那麼怎麼把第三方jar包(不是源碼)自動加入到IOC容器呢,這就是自動裝配需要解決的問題。
@EnableAutoConfiguration 里邊通過@Import(.class)的這個方法
public String[]selectImports(AnnotationMetadata annotationMetadata) 去搜素jar包中,META-INF文件夾下的xxx.factories來最終載入Configuration
從而實現了我們之前通過@Confituration載入業務類的行為。
㈣ Spring 源碼(九)@Autowired註解實現原理(Spring Bean的自動裝配)
@Autowired 註解的實現過程,其實就是Spring Bean的自動裝配過程。通過看@Autowired源碼注釋部分我們可以看到 @Autowired 的實現是通過 後置處理器中實現的。
在分析自動裝配前我們先來介紹一下上面的這些後置處理器。
BeanPostProcessor有兩個方法, 和 。它們分別在任何bean初始化回調之前或之後執行(例如InitializingBean的afterPropertiesSet方法或自定義init-method方法之前或者之後), 在這個時候該bean的屬性值已經填充完成了,並且我們返回的bean實例可能已經是原始實例的包裝類型了。例如返回一個 FactoryBean 。
繼承自 BeanPostProcessor 介面。主要多提供了以下三個方法。
該方法是在Bean實例化目標對象之前調用,返回的Bean對象可以代理目標,從而有效的阻止了目標Bean的默認實例化。
跟進源碼我們可以看出,如果此方法返回一個非null對象,則Bean創建過程將被短路。唯一應用的進一步處理是來自已配置BeanPostProcessors的回調。
該方法執行在通過構造函數或工廠方法在實例化bean之後但在發生Spring屬性填充(通過顯式屬性或自動裝配)之前執行操作。這是在Spring的自動裝配開始之前對給定的bean實例執行自定義欄位注入的理想回調。如果該方法返回false,那麼它會阻斷後續 後置處理器的執行,並且會阻止後續屬性填充的執行邏輯。
在工廠將給定屬性值應用於給定bean之前,對它們進行後處理。允許檢查是否滿足所有依賴關系,例如基於bean屬性設置器上的「 Required」註解。還允許替換要應用的屬性值,通常是通過基於原始PropertyValues創建新的MutablePropertyValues實例,添加或刪除特定值來實現。
智能實例化Bean的後處理器,主要提供了三個方法。
預測從此處理器的回調最終返回的bean的類型。
確定使用實例化Bean的構造函數。
獲取提早暴露的Bean的引用,提早暴露的Bean就是只完成了實例化,還未完成屬性賦值和初始化的Bean。
合並Bean的定義信息的後處理方法,該方法是在Bean的實例化之後設置值之前調用。
後置處理器主要負責對添加了@Autowired和@Value註解的元素實現自動裝配。所以找到需要自動裝配的元素,其實就是對@Autowired和@Value註解的解析。
後置處理器,找出需要自動裝配的元素是在 . 這個方法中實現的,調用鏈路如下:
從鏈路上我們可以看到,找到需要自動裝配的元素是在 findAutowiringMetadata 方法中實現的,該方法會去調用 buildAutowiringMetadata 方法構建元數據信息。如果註解被載入屬性上將會被封裝成 AutowiredFieldElement 對象;如果註解加在方法上,那麼元素會被封裝成 AutowiredMethodElement 對象。這里兩個對象的 inject 方法將最後完成屬性值的注入,主要區別就是使用反射注入值的方式不一樣。源碼如下:
尋找需要自動裝配過程:
後置處理器注入屬性值是在 postProcessPropertyValues 方法中實現的。源碼如下:
從這里的源碼我們可以看出 AutowiredFieldElement 和 AutowiredMethodElement 完成自動裝配都是先去容器中找對應的Bean,然後通過反射將獲取到的Bean設置到目標對象中,來完成Bean的自動裝配。
我們可以看出Spring Bean的自動裝配過程就是:
在自動裝配過程中還涉及循環依賴的問題,有興趣的可以看下這篇文章 Spring 源碼(八)循環依賴
㈤ 2020-07-26 帶著疑問看源碼 -- springboot aop默認採用什麼動態代理機制
了解springboot aop的動態代理方式有哪些種類?
有3種,前提均開啟spring.aop.auto=true:
1. jdk動態代理:當spring.aop.proxy-target-class=false, 引入了aspectjweaver依賴時生效
2. cglib代理:當spring.aop.proxy-target-class=true, 引入了aspectjweaver依賴時生效
3. 基礎代理:當spring.aop.proxy-target-class=true, 若沒有aspectjweaver依賴時生效,只作用於框架內部的advisors,
我們既然用springboot那麼就採用springboot的AopAutoConfiguration自動配置類來載入aop機制的,內部對@EnableAspectJAutoProxy進行了封裝,擴展了一些配置項,同時還提供了ClassProxyingConfiguration配置(下面會講到).
這個自動裝配類會是spring boot框架自動會裝配的,所以說默認aop機制是打開的,可以通過配置項:spring.aop.auto=false 手工關閉。
這個配置類會根據spring.aop.proxy-target-class配置項來決定採用jdk動態代理或者cglib動態代理:
注意:配置類生效前提是@ConditionalOnClass(Advice.class),說明只有當引入了依賴項才生效:
而這里推薦使用spring-boot-starter-aop來傳遞依賴:
這里我們已經看到提供了原生spring的兩種代理方式,接著看AopAutoConfiguration源碼發現還有個ClassProxyingConfiguration配置類,其生效條件之一是@ConditionalOnMissingClass("org.aspectj.weaver.Advice"),就是當項目里沒有aspectjweaver的依賴的時候生效。
我們進入AopConfigUtils.(registry)方法,通過幾步調用跳轉:
發現會去注冊後置處理器,查看源碼注釋:
表明了只為基礎的advisor做動態代理,而忽略應用定義的Advisors,說明項目中我們自定義的切面是不會被AOP代理的。
㈥ nacos原理
nacos目前是集成到spring cloud alibaba里去的,也就是在spring cloud的標准之下實現了一些東西,spring cloud自己是有一個介面,叫做ServiceRegistry,也就是服務注冊中心的概念,nacos中有一個它的實現類NacosServiceRegistry,實現了register、deregister、close、setStatus、getStatus之類的方法。
自動裝配是一個spring boot的一個概念,自動裝配的意思,其實就是說系統啟動的時候,自動裝配機制會運行,實現一些系統的初始化,自動運行,也就是系統啟動時自動去調用NacosServiceRegistry的register方法去進行服務注冊。而且除了注冊之外,還會通過schele線程池去提交一個定時調度任務,源碼如下:
this.exeutorService.schele(new BeatReactor.BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS),這就是一個心跳機制,定時發送心跳給nacos server。
然後會訪問nacos server的open api,其實就是http介面,他有一個介面:http://31.208.59.24:8848/nacos/v1/ns/instance?serviceName=xx&ip=xx&port=xx,這么一個東西,也沒什麼特別的,這里就是訪問注冊介面罷了
nacos server那裡是基於一個ConcurrentHashMap作為注冊表來放服務信息的,直接會構造一個Service放到map里,然後對Service去addInstance添加一個實例,本質裡面就是在維護信息,同時還會建立定時檢查實例心跳的機制。最後還會基於一致性協議,比如說raft協議,去把注冊同步給其他節點。
服務發現的本質其實也是nacos server上的一個http介面,就是:http://31.208.59.24:8848/nacos/v1/ns/instance/list?serviceName=xx,就這么一個介面,然後就會啟動定時任務,每隔10s拉取一次最新的實例列表,然後服務端還會監聽他服務的狀態,有異常就會基於UDP協議反向通知客戶端這次服務異常變動。
㈦ 徹底搞明白Spring中的自動裝配和Autowired
當Spring裝配Bean屬性時,有時候非常明確,就是需要將某個Bean的引用裝配給指定屬性。比如,如果我們的應用上下文中只有一個 org.mybatis.spring.SqlSessionFactoryBean 類型的Bean,那麼任意一個依賴 SqlSessionFactoryBean 的其他Bean就是需要這個Bean。畢竟這里只有一個 SqlSessionFactoryBean 的Bean。
為了應對這種明確的裝配場景,Spring提供了自動裝配(autowiring)。與其顯式的裝配Bean屬性,為何不讓Spring識別出可以自動裝配的場景。
當涉及到自動裝配Bean的依賴關系時,Spring有多種處理方式。因此,Spring提供了4種自動裝配策略。
Spring在 AutowireCapableBeanFactory 介面中定義了這幾種策略。其中, AUTOWIRE_AUTODETECT 被標記為過時方法,在Spring3.0之後已經不再支持。
它的意思是,把與Bean的屬性具有相同名字的其他Bean自動裝配到Bean的對應屬性中。聽起來可能比較拗口,我們來看個例子。
首先,在User的Bean中有個屬性 Role myRole ,再創建一個Role的Bean,它的名字如果叫myRole,那麼在User中就可以使用byName來自動裝配。
上面是Bean的定義,再看配置文件。
如上所述,只要屬性名稱和Bean的名稱可以對應,那麼在user的Bean中就可以使用byName來自動裝配。那麼,如果屬性名稱對應不上呢?
是的,如果不使用屬性名稱來對應,你也可以選擇使用類型來自動裝配。它的意思是,把與Bean的屬性具有相同類型的其他Bean自動裝配到Bean的對應屬性中。
還是上面的例子,如果使用byType,Role Bean的ID都可以省去。
它是說,把與Bean的構造器入參具有相同類型的其他Bean自動裝配到Bean構造器的對應入參中。值的注意的是, 具有相同類型的其他Bean 這句話說明它在查找入參的時候,還是通過Bean的類型來確定。
構造器中入參的類型為Role
它首先會嘗試使用constructor進行自動裝配,如果失敗再嘗試使用byType。不過,它在Spring3.0之後已經被標記為 @Deprecated 。
默認情況下,default-autowire屬性被設置為none,標示所有的Bean都不使用自動裝配,除非Bean上配置了autowire屬性。
如果你需要為所有的Bean配置相同的autowire屬性,有個辦法可以簡化這一操作。
在根元素Beans上增加屬性 default-autowire="byType" 。
Spring自動裝配的優點不言而喻。但是事實上,在Spring XML配置文件里的自動裝配並不推薦使用,其中筆者認為最大的缺點在於不確定性。或者除非你對整個Spring應用中的所有Bean的情況了如指掌,不然隨著Bean的增多和關系復雜度的上升,情況可能會很糟糕。
從Spring2.5開始,開始支持使用註解來自動裝配Bean的屬性。它允許更細粒度的自動裝配,我們可以選擇性的標注某一個屬性來對其應用自動裝配。
Spring支持幾種不同的應用於自動裝配的註解。
我們今天只重點關注Autowired註解,關於它的解析和注入過程,請參考筆者Spring源碼系列的文章。 Spring源碼分析(二)bean的實例化和IOC依賴注入
使用@Autowired很簡單,在需要注入的屬性加入註解即可。
不過,使用它有幾個點需要注意。
默認情況下,它具有強制契約特性,其所標注的屬性必須是可裝配的。如果沒有Bean可以裝配到Autowired所標注的屬性或參數中,那麼你會看到 NoSuchBeanDefinitionException 的異常信息。
看到上面的源碼,我們可以得到這一信息,Bean集合為空不要緊,關鍵 isRequired 條件不能成立,那麼,如果我們不確定屬性是否可以裝配,可以這樣來使用Autowired。
我記得曾經有個面試題是這樣問的:Autowired是按照什麼策略來自動裝配的呢?
關於這個問題,不能一概而論,你不能簡單的說按照類型或者按照名稱。但可以確定的一點的是,它默認是按照類型來自動裝配的,即byType。
關鍵點 findAutowireCandidates 這個方法。
可以看到它返回的是一個列表,那麼就表明,按照類型匹配可能會查詢到多個實例。到底應該裝配哪個實例呢?我看有的文章里說,可以加註解以此規避。比如 @qulifier、@Primary 等,實際還有個簡單的辦法。
比如,按照UserService介面類型來裝配它的實現類。UserService介面有多個實現類,分為 UserServiceImpl、UserServiceImpl2 。那麼我們在注入的時候,就可以把屬性名稱定義為Bean實現類的名稱。
這樣的話,Spring會按照byName來進行裝配。首先,如果查到類型的多個實例,Spring已經做了判斷。
可以看出,如果查到多個實例, determineAutowireCandidate 方法就是關鍵。它來確定一個合適的Bean返回。其中一部分就是按照Bean的名稱來匹配。
最後我們回到問題上,得到的答案就是:@Autowired默認使用byType來裝配屬性,如果匹配到類型的多個實例,再通過byName來確定Bean。
上面我們已經看到了,通過byType可能會找到多個實例的Bean。然後再通過byName來確定一個合適的Bean,如果通過名稱也確定不了呢?
還是 determineAutowireCandidate 這個方法,它還有兩種方式來確定。
它的作用是看Bean上是否包含@Primary註解,如果包含就返回。當然了,你不能把多個Bean都設置為@Primary,不然你會得到 這個異常。
你也可以在Bean上配置@Priority註解,它有個int類型的屬性value,可以配置優先順序大小。數字越小的,就被優先匹配。同樣的,你也不能把多個Bean的優先順序配置成相同大小的數值,否則 異常照樣出來找你。
最後,有一點需要注意。Priority的包在 javax.annotation.Priority; ,如果想使用它還要引入一個坐標。
本章節重點闡述了Spring中的自動裝配的幾種策略,又通過源碼分析了Autowired註解的使用方式。
在Spring3.0之後,有效的自動裝配策略分為 byType、byName、constructor 三種方式。註解Autowired默認使用byType來自動裝配,如果存在類型的多個實例就嘗試使用byName匹配,如果通過byName也確定不了,可以通過Primary和Priority註解來確定。
㈧ 波場的自動歸集有源碼嘛
有。
波場的自動歸集有源碼,源碼無加密,帶說明。自動歸集至冷錢包地址,安全可靠。無需向用戶或訂單地址注入Trx即可完成Trx/TRC20代幣的歸集。
㈨ 「SpringCloud原理」Ribbon核心組件以及運行原理萬字源碼剖析
大家好,本文我將繼續來剖析SpringCloud中負載均衡組件Ribbon的源碼。本來我是打算接著OpenFeign動態代理生成文章直接講Feign是如何整合Ribbon的,但是文章寫了一半發現,如果不把Ribbon好好講清楚,那麼有些Ribbon的細節理解起來就很困難,所以我還是打算單獨寫一篇文章來剖析Ribbon的源碼,這樣在講Feign整合Ribbon的時候,我就不再贅述這些細節了。好了,話不多說,直接進入主題。
這是個很簡單的東西,就是服務實例數據的封裝,裡面封裝了服務實例的ip和埠之類的,一個服務有很多台機器,那就有很多個Server對象。
ServerList是個介面,泛型是Server,提供了兩個方法,都是獲取服務實例列表的,這兩個方法其實在很多實現類中實現是一樣的,沒什麼區別。這個介面很重要,因為這個介面就是Ribbon獲取服務數據的來源介面,Ribbon進行負載均衡的服務列表就是通過這個介面來的,那麼可以想一想是不是只要實現這個介面就可以給Ribbon提供服務數據了?事實的確如此,在SpringCloud中,eureka、nacos等注冊中心都實現了這個介面,都將注冊中心的服務實例數據提供給Ribbon,供Ribbon來進行負載均衡。
通過名字也可以知道,是用來更新服務注冊表的數據,他有唯一的實現,就是PollingServerListUpdater,這個類有一個核心的方法,就是start,我們來看一下start的實現。
通過這段方法我們可以看出,首先通過isActive.compareAndSet(false, true)來保證這個方法只會被調用一下,然後封裝了一個Runnable,這個Runnable幹了一件核心的事,就是調用傳入的updateAction的doUpdate方法,然後將Runnable扔到了帶定時調度功能的線程池,經過initialDelayMs(默認1s)時間後,會調用一次,之後都是每隔refreshIntervalMs(默認30s)調用一次Runnable的run方法,也就是調用updateAction的doUpdate方法。
所以這個類的核心作用就是每隔30s會調用一次傳入的updateAction的doUpdate方法的實現,記住這個結論。
IRule是負責負載均衡的演算法的,也就是真正實現負載均衡獲取一個服務實例就是這個介面的實現。比如說實現類RandomRule,就是從一堆服務實例中隨機選取一個服務實例。
就是一個配置介面,有個默認的實現DefaultClientConfigImpl,通過這個可以獲取到一些配置Ribbon的一些配置。
這個介面的作用,對外主要提供了獲取服務實例列表和選擇服務實例的功能。雖然對外主要提供獲取服務的功能,但是在實現的時候,主要是用來協調上面提到的各個核心組件的,使得他們能夠協調工作,從而實現對外提供獲取服務實例的功能。
這個介面的實現有好幾個實現類,但是我講兩個比較重要的。
BaseLoadBalancer
核心屬性
allServerList:緩存了所有的服務實例數據
upServerList:緩存了能夠使用的服務實例數據。
rule:負載均衡演算法組件,默認是RoundRobinRule
核心方法
setRule:這個方法是設置負載均衡演算法的,並將當前這個ILoadBalancer對象設置給IRule,從這可以得出一個結論,IRule進行負載均衡的服務實例列表是通過ILoadBalancer獲取的,也就是 IRule 和 ILoadBalancer相互引用。setRule(rule)一般是在構造對象的時候會調用。
chooseServer:就是選擇一個服務實例,是委派給IRule的choose方法來實現服務實例的選擇。
BaseLoadBalancer這個實現類總體來說,已經實現了ILoadBalancer的功能的,所以這個已經基本滿足使用了。
說完BaseLoadBalancer這個實現類,接下來說一下DynamicServerListLoadBalancer實現類。DynamicServerListLoadBalancer繼承自BaseLoadBalancer,DynamicServerListLoadBalancer主要是對BaseLoadBalancer功能進行擴展。
DynamicServerListLoadBalancer
成員變數
serverListImpl:上面說過,通過這個介面獲取服務列表
filter:起到過濾的作用,一般不care
updateAction:是個匿名內部類,實現了doUpdate方法,會調用updateListOfServers方法
serverListUpdater:上面說到過,默認就是唯一的實現類PollingServerListUpdater,也就是每個30s就會調用傳入的updateAction的doUpdate方法。
這不是巧了么,serverListUpdater的start方法需要一個updateAction,剛剛好成員變數有個updateAction的匿名內部類的實現,所以serverListUpdater的start方法傳入的updateAction的實現其實就是這個匿名內部類。
那麼哪裡調用了serverListUpdater的start方法傳入了updateAction呢?是在構造的時候調用的,具體的調用鏈路是調用 restOfInit -> (),這里就不貼源碼了
所以,其實DynamicServerListLoadBalancer在構造完成之後,默認每隔30s中,就會調用updateAction的匿名內部類的doUpdate方法,從而會調用updateListOfServers。所以我們來看一看 updateListOfServers 方法幹了什麼。
這個方法實現很簡單,就是通過調用 ServerList 的getUpdatedListOfServers獲取到一批服務實例數據,然後過濾一下,最後調用updateAllServerList方法,進入updateAllServerList方法。
其實很簡單,就是調用每個服務實例的setAlive方法,將isAliveFlag設置成true,然後調用setServersList。setServersList這個方法的主要作用是將服務實例更新到內部的緩存中,也就是上面提到的allServerList和upServerList,這里就不貼源碼了。
其實分析完updateListOfServers方法之後,再結合上面源碼的分析,我們可以清楚的得出一個結論,那就是默認每隔30s都會重新通過ServerList組件獲取到服務實例數據,然後更新到BaseLoadBalancer緩存中,IRule的負載均衡所需的服務實例數據,就是這個內部緩存。
從DynamicServerListLoadBalancer的命名也可以看出,他相對於父類BaseLoadBalancer而言,提供了動態更新內部服務實例列表的功能。
為了便於大家記憶,我畫一張圖來描述這些組件的關系以及是如何運作的。
說完一些核心的組件,以及他們跟ILoadBalancer的關系之後,接下來就來分析一下,ILoadBalancer是在ribbon中是如何使用的。
ILoadBalancer是一個可以獲取到服務實例數據的組件,那麼服務實例跟什麼有關,那麼肯定是跟請求有關,所以在Ribbon中有這么一個抽象類,,這個是用來執行請求的,我們來看一下這個類的構造。
通過上面可以看出,在構造的時候需要傳入一個ILoadBalancer。
中有一個方法executeWithLoadBalancer,這個是用來執行傳入的請求,以負載均衡的方式。
這個方法構建了一個LoadBalancerCommand,隨後調用了submit方法,傳入了一個匿名內部類,這個匿名內部類中有這么一行代碼很重要。
這行代碼是根據給定的一個Server重構了URI,這是什麼意思呢?舉個例子,在OpenFeign那一篇文章我說過,會根據服務名拼接出類似 http:// ServerA 的地址,那時是沒有伺服器的ip地址的,只有服務名,假設請求的地址是 http:// ServerA/api/sayHello ,那麼reconstructURIWithServer乾的一件事就是將ServerA服務名替換成真正的服務所在的機器的ip和埠,假設ServerA所在的一台機器(Server裡面封裝了某台機器的ip和埠)是192.168.1.101:8088,那麼重構後的地址就變成 http:// 192.168.1.101:8088/api/ sayHello ,這樣就能發送http請求到ServerA服務所對應的一台伺服器了。
之後根據新的地址,調用這個類中的execute方法來執行請求,execute方法是個抽象方法,也就是交給子類實現,子類就可以通過實現這個方法,來發送http請求,實現rpc調用。
那麼這台Server是從獲取的呢?其實猜猜也知道,肯定是通過ILoadBalancer獲取的,因為submit方法比較長,這里我直接貼出submit方法中核心的一部分代碼
就是通過selectServer來選擇一個Server的,selectServer我就不翻源碼了,其實最終還是調用ILoadBalancer的方法chooseServer方法來獲取一個服務,之後就會調用上面的說的匿名內部類的方法,重構URI,然後再交由子類的execut方法來實現發送http請求。
所以,通過對的executeWithLoadBalancer方法,我們可以知道,這個抽象類的主要作用就是通過負載均衡演算法,找到一個合適的Server,然後將你傳入的請求路徑 http:// ServerA/api/sayHello 重新構建成類似 http:// 192.168.1.101:8088/api/ sayHello 這樣,之後調用子類實現的execut方法,來發送http請求,就是這么簡單。
到這里其實Ribbon核心組件和執行原理我就已經說的差不多了,再來畫一張圖總結一下
說完了Ribbon的一些核心組件和執行原理之後,我們再來看一下在SpringCloud環境下,這些組件到底是用的哪些實現,畢竟有寫時介面,有的是抽象類。
Ribbon的自動裝配類:RibbonAutoConfiguration,我拎出了核心的源碼
RibbonAutoConfiguration配置類上有個@RibbonClients註解,接下來講解一下這個註解的作用
SpringClientFactory是不是感覺跟OpenFeign中的FeignContext很像,其實兩個的作用是一樣的,SpringClientFactory也繼承了NamedContextFactory,實現了配置隔離,同時也在構造方法中傳入了每個容器默認的配置類RibbonClientConfiguration。至於什麼是配置隔離,我在OpenFeign那篇文章說過,不清楚的小夥伴可以後台回復feign01即可獲得文章鏈接。
配置優先順序問題
優先順序最低的就是FeignContext和SpringClientFactory構造時傳入的配置類
至於優先順序怎麼來的,其實是在NamedContextFactory中createContext方法中構建時按照配置的優先順序一個一個傳進去的。
RibbonClientConfiguration提供的默認的bean
接下來我們看一下RibbonClientConfiguration都提供了哪些默認的bean
配置類對應的bean,這里設置了ConnectTimeout和ReadTimeout都是1s中。
IRule,默認是ZoneAvoidanceRule,這個Rule帶有過濾的功能,過濾哪些不可用的分區的服務(這個過濾可以不用care),過濾成功之後,繼續採用線性輪詢的方式從過濾結果中選擇一個出來。至於這個propertiesFactory,可以不用管,這個是默認讀配置文件的中的配置,一般不設置,後面看到都不用care。
至於為什麼容器選擇NacosServerList而不是ConfigurationBasedServerList,主要是因為這個配置類是通過@RibbonClients導入的,也就是比SpringClientFactory導入的RibbonClientConfiguration配置類優先順序高。
ServerListUpdater,就是我們剖析的PollingServerListUpdater,默認30s更新一次BaseLoadBalancer內部服務的緩存。
那麼在springcloud中,上圖就可以加上注冊中心。
三、總結
本文剖析了Ribbon這個負載均衡組件中的一些核心組件的源碼,並且將這些組件之間的關系一一描述清楚,同時也剖析了在發送請求的時候是如何通過ILoadBalancer獲取到一個服務實例,重構URI的過程。希望本篇文章能夠讓你知道Ribbon是如何工作的。