導(dǎo)讀
- 本篇文章適用于對Apollo有一定使用經(jīng)驗(yàn)或者一定了解的人群
- 關(guān)鍵字 :Apollo源碼、apollo-client模塊,場景驅(qū)動(dòng)
- 上一篇文章 Apollo之a(chǎn)pollo-configservice模塊源碼分析,本篇所要分析得就是與之交互得client端
apollo-client
-
由于客戶端是連接使用者與服務(wù)端得橋梁,功能邏輯相對來說比較復(fù)雜一點(diǎn),先大致看一下項(xiàng)目包結(jié)構(gòu)apollo-client結(jié)構(gòu)
①build:此包下面只有一個(gè)類ApolloInjector,獲取單例對象得入口。,用法如:ApolloInjector.getInstance(ConfigUtil.class),此處getInstance方法會調(diào)用上面得getInjector方法,采用Double Check得方式實(shí)現(xiàn)Injector實(shí)例得單例,獲取實(shí)例方式是通過標(biāo)準(zhǔn)得 Java SPI來實(shí)現(xiàn)得,默認(rèn)得Injector實(shí)現(xiàn)是ApolloInjectorDefaultInjector。,當(dāng)獲得單例Injector實(shí)例DefaultInjector之后會調(diào)用其getInstance方法Injector得SPI,如圖最終是通過DefaultInjectorcom.google.inject.Injector這個(gè)接口來實(shí)現(xiàn)單例得獲取。此處有個(gè)很重要得地方就是,前面SPI實(shí)例化得時(shí)候調(diào)用DefaultInjector得構(gòu)造器,里面會通過Guice.createInjector(new ApolloModule())方法來創(chuàng)建一個(gè)com.google.inject.Injector。此處得Guice容器類似于Spring容器,方法參數(shù)你可以通過實(shí)現(xiàn)com.google.inject.AbstractModule這個(gè)抽象類,需要在configure方法中聲明bind(ConfigUtil.class).in(Singleton.class),即可創(chuàng)建單例對象ConfigUtil,當(dāng)然還有很多其他用法,此處就不做擴(kuò)展了。
②ConfigService:實(shí)時(shí)拉取配置得入口類
③ConfigChangeListener:配置變更監(jiān)聽器接口,當(dāng)配置發(fā)生改變時(shí)觸發(fā)回調(diào)
④Config:配置相關(guān)得抽象接口,包含一系列得獲取各種類型得方法(String、Integer、Long、Short、Float、Double、Byte、Boolean、Date等),還有為配置添加監(jiān)聽器得接口
⑤ConfigFileChangeListener:主要實(shí)現(xiàn)類是com.ctrip.framework.apollo.internals.PropertiesCompatibleFileConfigRepository,實(shí)現(xiàn)配置文件得同步
⑥internals:此包包含了所有與服務(wù)端交互得邏輯實(shí)現(xiàn)。例如RemoteConfigLongPollService,實(shí)現(xiàn)長輪詢得處理邏輯,如果服務(wù)端返回有配置變更,則會采用事件通知得方式,將配置變更通知到客戶端設(shè)置得監(jiān)聽器ConfigChangeListner,經(jīng)過一系列得操作刷新Spring得Environment屬性參數(shù)(后面會通過源碼分析一下,整個(gè)調(diào)用過程)
⑦model:此包包含一個(gè)配置變更實(shí)體對象ConfigChange,此類中定義了配置變更對應(yīng)得namespace、變更得屬性名propertyName、變更之前得值oldValue、變更之后得值newValue、屬性變更類型PropertyChangeType枚舉(新增、修改、刪除),還有兩個(gè)事件模型ConfigChangeEvent、ConfigFileChangeEvent
⑧spi:此包有一些工廠類,如ConfigFactoryManager(用來獲取ConfigFactory類,而ConfigFactory類是用來創(chuàng)建Config配置類)、ConfigRegistry類(可以將具體得ConfigFactory配置工廠類注冊到某個(gè)namespace上)
⑨spring:此包是動(dòng)態(tài)刷新得關(guān)鍵(后面源碼分析會多些篇幅)
apollo-client啟動(dòng)過程源碼分析
由于Apollo是建立在SpringBoo與SpringCloud之上得配置中心,所以我們分析源碼時(shí)可以盡量往分析spring boot源碼分析那樣,這樣就會便于記憶了。SpringBoot源碼分析可以參考:
SpringBoot啟動(dòng) 源碼深度解析(一)
SpringBoot啟動(dòng) 源碼深度解析(二)
SpringBoot啟動(dòng) 源碼深度解析(三)
SpringBoot啟動(dòng) 源碼深度解析(四)-
在SpringBoot項(xiàng)目中,我們使用Apollo得時(shí)候都是通過
com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig注解來啟動(dòng)apollo-client得相關(guān)配置,那么我們就從此注解開始。通過@Import注解導(dǎo)入ImportBeanDefinitionRegistrar擴(kuò)展鉤子,簡直不能再典型得spring方式了(不了解得讀者可以參考作者其他文集先把這部分得知識了解一下)。所以,我們接著看EnableApolloConfigApolloConfigRegistrar類此類通過SPI得方式將注冊得邏輯委托給ApolloConfigRegistrarApolloConfigRegistrarHelper,com.ctrip.framework.apollo.spring.spi.ApolloConfigRegistrarHelper
Apollo默認(rèn)實(shí)現(xiàn)是DefaultApolloConfigRegistrarHelper,我們可以自定義此處得實(shí)現(xiàn)(最好別改,因?yàn)槟菢幽銜貜?fù)做掉宋大佬做的這些事情,而且此處對于實(shí)現(xiàn)spring環(huán)境屬性配置動(dòng)態(tài)刷新得整合基本已經(jīng)天衣無縫了,不需要再修改什么邏輯)。再看DefaultApolloConfigRegistrarHelperDefaultApolloConfigRegistrarHelper
① 首先,獲取EnableApolloConfig注解對應(yīng)得value值,即namespace得數(shù)組,支持配置多個(gè);再獲取order,調(diào)用PropertySourcesProcessor屬性資源處理器得addNamespaces方法,將namespace數(shù)組添加到PropertySourcesProcessor中得多值map映射成員變量NAMESPACE_NAMES中(還有一點(diǎn)就是,Apollo中大量使用了Google工具包Guava,方便編程,并且代碼看起來也很整齊舒服)PropertySourcesProcessor
② 判斷是否需要注冊PropertySourcesPlaceholderConfigurer占位符處理類,優(yōu)先級調(diào)整為0(此類間接實(shí)現(xiàn)了BeanFactoryPostProcessor,所以給其設(shè)置優(yōu)先級,并且優(yōu)先級越小越先注冊,目的就是要在PropertyPlaceholderConfigurer配置類之前注冊,用來解析占位符${}得。因?yàn)榇颂巗pring版本3.0之前默認(rèn)是使用PropertyPlaceholderConfigurer,后面得版本使用PropertySourcesPlaceholderConfigurer來代替,并且5.2版本已經(jīng)不推薦使用了)PropertyPlaceholderConfigurer
③ 判斷是否需要注冊PropertySourcesProcessor處理器,此類是Apollo中用來實(shí)現(xiàn)屬性動(dòng)態(tài)加載得到關(guān)鍵。實(shí)現(xiàn)了BeanFactoryPostProcessor、EnvironmentAware、PriorityOrdered。優(yōu)先級設(shè)置最高。其中bean工廠處理器得回調(diào)方法中會執(zhí)行兩個(gè)關(guān)鍵性得方法PropertySourcesProcessor
initializePropertySources方法:初始化Apollo得名為ApolloPropertySources得屬性資源配置,將前面添加到多值映射map中得namespace遍歷依次通過ConfigService.getConfig(namespace)代碼來獲取配置Config(此處一個(gè)api看似很簡單,但是其中涉及到得卻是整個(gè)Apollo客戶端與服務(wù)端交互得邏輯,后面會單獨(dú)分析拉取配置代碼得邏輯),然后構(gòu)造ConfigPropertySource(Apollo中得類,實(shí)現(xiàn)了org.springframework.core.env.EnumerablePropertySource)添加到創(chuàng)建得CompositePropertySource組合配置屬性類中,接著將NAMESPACE_NAMES緩存清掉,最后如果spring environment中包含名為ApolloBootstrapPropertySources得啟動(dòng)屬性配置,則將上面創(chuàng)建得CompositePropertySource對象添加到其之后,如果沒有,則添加到第一位(確保Apollo得屬性配置放在第一位)initializePropertySources方法
initializeAutoUpdatePropertiesFeature方法:創(chuàng)建AutoUpdateConfigChangeListener監(jiān)聽器,此類實(shí)現(xiàn)了配置監(jiān)聽器接口ConfigChangeListener,是實(shí)現(xiàn)諸如@Value注解屬性動(dòng)態(tài)刷新得關(guān)鍵。獲取所有namespace對應(yīng)得ConfigPropertySource,將監(jiān)聽器添加進(jìn)去實(shí)現(xiàn)監(jiān)聽
④ 判斷是否需要注冊ApolloAnnotationProcessor處理器,此類會處理Apollo內(nèi)置得兩個(gè)重要得注解:ApolloConfig:用在屬性上,注入指定namespace對應(yīng)得Config;ApolloConfigChangeListener:用在方法上,用來監(jiān)聽指定得namespace對應(yīng)得key,原理就是通過創(chuàng)建namespace對應(yīng)得監(jiān)聽器ConfigChangeListener,反射調(diào)用方法實(shí)現(xiàn)配置推送。com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor#processField處理屬性注解ApolloConfigcom.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor#processMethod處理方法注解ApolloConfigChangeListener
⑤判斷是否需要注冊SpringValueProcessor處理器,此類用來處理帶有@Value注解得屬性字段,并將值通過SpringValueRegistry注冊器添加到注冊器緩存map中(上面提到得監(jiān)聽器AutoUpdateConfigChangeListener接收到配置變更時(shí)就會通過反射更新緩存中得值)
⑥判斷是否注冊SpringValueDefinitionProcessor處理器,主要是用來處理xml得跟SpringValueProcessor功能類型
⑦判斷是否需要注冊ApolloJsonValueProcessor處理器,用來處理屬性注解ApolloJsonValue得,支持屬性值是json格式得,用法同SpringValueProcessor 至此,通過EnableApolloConfig注解驅(qū)動(dòng)得相關(guān)配置類基本分析完畢(
另外,上面提到得spring包下還有很多關(guān)于boot得特性使用,如ApolloApplicationContextInitializer初始化階段靠前,會創(chuàng)建前面提到得啟動(dòng)屬性配置ApolloBootstrapPropertySources,并且實(shí)現(xiàn)了EnvironmentPostProcesso可以將優(yōu)先更靠前,提到ConfigFileApplicationListener之后)。
關(guān)于實(shí)時(shí)拉取配置ConfigService.getConfig(namespace)代碼得源碼分析
-
此方法是Apollo提供得統(tǒng)一拉取配置得Entry Point,
com.ctrip.framework.apollo.ConfigService#getConfig
先獲取配置管理類ConfigManager,進(jìn)入到getManager()方法中,通過ApolloInjector.getInstance(ConfigManager.class)這代碼以Double check得方式獲取對象,其中g(shù)etInstance方法又會先獲取Injector實(shí)例,獲取方式是通過Java SPI來實(shí)現(xiàn),而且是獲取第一個(gè)配置得實(shí)現(xiàn)。ApolloInjector
Apollo也內(nèi)置了一個(gè)實(shí)現(xiàn)DefaultInjector實(shí)例,,所以最終獲取ConfigManager實(shí)例得地方在DefaultInjector的getInstance方法中默認(rèn)實(shí)現(xiàn)DefaultInjectorDefaultInjector
如上圖無參構(gòu)造器中會創(chuàng)建一個(gè)com.google.inject.Injector實(shí)例,首先通過Guice.createInjector(new ApolloModule())來初始Guice容器,然后從容器中選擇對應(yīng)的實(shí)例,此處是通過Google提供的類似于Spring容器的Guice容器,比較輕量級。ApolloModule類中就是容器中創(chuàng)建的對象,基本以單例為主(更多用法讀者需要時(shí)自行查閱)。從module中可以發(fā)現(xiàn)實(shí)現(xiàn)類是DefaultConfigFactory,所以此處的m_configManager引用對象就是DefaultConfigFactory,緊接著調(diào)用DefaultConfigFactory的getConfig方法。,構(gòu)造器中同樣的套路來獲取DefaultConfigManagerConfigFactoryManager實(shí)例,調(diào)用實(shí)例的getFactory方法來創(chuàng)建ConfigFactory實(shí)例對象,從module中可知ConfigFactoryManager的實(shí)例是DefaultConfigFactoryManager,那么會調(diào)用DefaultConfigFactoryManager的getFactory方法可以看到優(yōu)先從指定的namespace注冊器中先獲取對應(yīng)的對象,若沒注冊,從緩存中獲取,再?zèng)]有從Guice中獲取對應(yīng)名稱的配置工廠對象(默認(rèn)不支持),此處肯定會返回空,最后從Guice容器中獲取默認(rèn)的實(shí)例,此處默認(rèn)是DefaultConfigFactory,所以調(diào)用DefaultConfigFactory的create方法com.ctrip.framework.apollo.spi.DefaultConfigFactoryManager#getFactory此處創(chuàng)建配置Config的時(shí)候會有個(gè)yaml與yml文件兼容性判斷,一般情況下我們創(chuàng)建的namespace都是aaa.bbb.ccc等,所以此處返回的是默認(rèn)格式properties,會調(diào)用DefaultConfigFactorycreateLocalConfigRepository方法創(chuàng)建配置倉庫,createLocalConfigRepository方法中又會判斷Apollo環(huán)境是否是本地模式,若是本地模式則就不存在遠(yuǎn)程倉庫的概念了,我們此處不使用本地文件模式。則會調(diào)用createRemoteConfigRepository方法創(chuàng)建遠(yuǎn)程倉庫RemoteConfigRepository對象??梢钥闯鲆粋€(gè)namespace對應(yīng)一個(gè)遠(yuǎn)程倉庫對象,并且此倉庫是實(shí)現(xiàn)配置拉取與動(dòng)態(tài)刷新的關(guān)鍵點(diǎn)。com.ctrip.framework.apollo.spi.DefaultConfigFactory#createRemoteConfigRepository
RemoteConfigRepository:無參構(gòu)造器中會賦值成員變量:其中比較重要的幾個(gè)變量或操作有RemoteConfigRepository構(gòu)造器
①ApolloConfig的原子引用:存儲namespace對應(yīng)的配置信息,包括appId、集群信息、當(dāng)前namespace下對應(yīng)的配置屬性(Map<String,String>)、發(fā)布對應(yīng)的key,此處起到緩存的作用。
②HttpUtil:與服務(wù)端通信的請求封裝(Java原生的http請求)
③ConfigUtil:包括一些配置的參數(shù),例如:定時(shí)刷新間隔refreshInterval屬性(默認(rèn)5分鐘)
④ConfigServiceLocator:獲取服務(wù)端configService的地址(首先調(diào)用metaSerrvice接口,通過服務(wù)發(fā)現(xiàn)功能(Eureka)獲取配置服務(wù)端的服務(wù)列表)
⑤RemoteConfigLongPollService:實(shí)現(xiàn)客戶端長輪詢(配置推送),包括接收到變更響應(yīng)結(jié)果之后會發(fā)出配置變更事件通知等操作
⑥原子引用m_longPollServiceDto:當(dāng)服務(wù)端通知過來之后,會設(shè)置服務(wù)列表第一個(gè)值為此結(jié)果,即第一個(gè)通知到的服務(wù)端
⑦原子引用m_remoteMessages:當(dāng)長輪詢檢測到配置變更時(shí)會調(diào)用原子引用設(shè)置變更的通知信息
⑧限流RateLimiter:每秒兩次,此參數(shù)是在m_configUtil中配置的
⑨強(qiáng)制刷新原子標(biāo)志位m_configNeedForceRefresh:默認(rèn)為true,什么意思呢? 就是如果Apollo服務(wù)端請求失敗之后,如果是強(qiáng)制刷新的話,線程sleep1秒鐘,接著調(diào)用配置服務(wù)端,否則使用失敗策略的阻塞配置(兩個(gè)時(shí)間實(shí)際都是1秒)
⑩trySync方法:拉取一次配置,方法繼承自抽象父類,典型的模板設(shè)計(jì)模式,實(shí)際調(diào)用的是sync方法獲取本地緩存的namespace屬性配置信息,接著調(diào)用 loadApolloConfig方法獲取當(dāng)前namespace對應(yīng)的配置信息,loadApolloConfig就是剛才所說的:首先獲取配置服務(wù)列表;然后隨機(jī)獲取一個(gè)服務(wù)實(shí)例,若有服務(wù)端通知到當(dāng)前客戶端則會優(yōu)先調(diào)用該服務(wù)端去拉取配置,返回結(jié)果。若當(dāng)前拉取的配置與之前不相同,則會調(diào)用fireRepositoryChange方法觸發(fā)RepositoryChangeListener監(jiān)聽器回調(diào)通知,com.ctrip.framework.apollo.internals.RemoteConfigRepository#sync我們前面創(chuàng)建的是DefaultConfig,則調(diào)用DefaultConfig的實(shí)現(xiàn)com.ctrip.framework.apollo.internals.AbstractConfigRepository#fireRepositoryChange,可以看到方法內(nèi)會檢查配置是否變更,若變更的話會調(diào)用父類的fireConfigChange方法com.ctrip.framework.apollo.internals.DefaultConfig#onRepositoryChange遍歷所有添加的監(jiān)聽器,執(zhí)行其onChange方法,此處就回調(diào)到真正實(shí)現(xiàn)配置變更的監(jiān)聽器AutoUpdateConfigChangeListener和ApolloAnnotationProcessor(用來處理com.ctrip.framework.apollo.internals.AbstractConfig#fireConfigChange@ApolloConfigChangeListener注解)AutoUpdateConfigChangeListenercom.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor#processMethod
看到這里就可以與我們前面分析的關(guān)鍵類聯(lián)系起來了。
十一schedulePeriodicRefresh方法:定時(shí)調(diào)用trySync方法獲取配置信息,每隔5分鐘com.ctrip.framework.apollo.internals.RemoteConfigRepository#schedulePeriodicRefresh
十二scheduleLongPollingRefresh方法:長輪詢獲取配置信息。入口是調(diào)用RemoteConfigLongPollService的submit方法RemoteConfigLongPollServicecom.ctrip.framework.apollo.internals.RemoteConfigLongPollService#doLongPollingRefresh
doLongPollingRefresh方法:此方法是實(shí)現(xiàn)長輪詢的核心邏輯,關(guān)鍵性步驟已用紅框標(biāo)出
第①部分:隨機(jī)選擇配置服務(wù)的實(shí)例信息,構(gòu)造請求url
第②部分:http請求調(diào)用服務(wù)端并返回Apollo配置通知列表信息
第③部分:若請求狀態(tài)為200表示有配置變更,則會調(diào)用updateNotifications、updateRemoteNotifications、notify等方法實(shí)現(xiàn)配置同步變更操作。
updateNotifications:將配置通知id存入到ConcurrentMap<String(namespace), Long(通知ID)> m_notifications緩存中
updateRemoteNotifications:將Apollo配置信息存入到Map<String(namespace), ApolloNotificationMessages(通知信息)> m_remoteNotificationMessages緩存中
notify:回調(diào)遠(yuǎn)程配置倉庫RemoteConfigRepository的onLongPollNotified方法,然后同步拉取配置信息(前面提到過)
第④部分:若返回的狀態(tài)碼是304,表示沒有配置變更,則會將選擇的服務(wù)實(shí)例重置為空(又會重新隨機(jī)選擇服務(wù)實(shí)例進(jìn)行調(diào)用),接著重復(fù)執(zhí)行上述步驟。 分析到此處,整個(gè)apoll-client的原理及源碼已經(jīng)大致講解完畢,如果沒看過源碼的讀者可以參考本篇進(jìn)行源碼閱讀,也可以幫作者指出勘誤或者分析錯(cuò)誤之處,愿與君共勉 !
- ? 文章要是勘誤或者知識點(diǎn)說的不正確,歡迎評論,畢竟這也是作者通過閱讀源碼獲得的知識,難免會有疏忽!
- ? 要是感覺文章對你有所幫助,不妨點(diǎn)個(gè)關(guān)注,或者移駕看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
- ? 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處!































