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