导读
- 本篇文章适用于对Apollo有一定使用经验或者一定了解的人群
- 关键字 :Apollo源码、apollo-client模块,场景驱动
- 上一篇文章 Apollo之apollo-configservice模块源码分析,本篇所要分析得就是与之交互得client端
apollo-client
-
由于客户端是连接使用者与服务端得桥梁,功能逻辑相对来说比较复杂一点,先大致看一下项目包结构
apollo-client结构
①build:
此包下面只有一个类ApolloInjector
,获取单例对象得入口。 ApolloInjector ,用法如:ApolloInjector.getInstance(ConfigUtil.class),此处getInstance方法会调用上面得getInjector方法,采用Double Check得方式实现Injector实例得单例,获取实例方式是通过标准得 Java SPI来实现得,默认得Injector实现是DefaultInjector
。 Injector得SPI ,当获得单例Injector实例DefaultInjector之后会调用其getInstance方法 DefaultInjector ,如图最终是通过com.google.inject.Injector
这个接口来实现单例得获取。此处有个很重要得地方就是,前面SPI实例化得时候调用DefaultInjector得构造器,里面会通过Guice.createInjector(new ApolloModule())
方法来创建一个com.google.inject.Injector。此处得Guice容器类似于Spring容器,方法参数你可以通过实现com.google.inject.AbstractModule
这个抽象类,需要在configure方法中声明bind(ConfigUtil.class).in(Singleton.class)
,即可创建单例对象ConfigUtil
,当然还有很多其他用法,此处就不做扩展了。
②ConfigService:
实时拉取配置得入口类
③ConfigChangeListener:
配置变更监听器接口,当配置发生改变时触发回调
④Config:
配置相关得抽象接口,包含一系列得获取各种类型得方法(String、Integer、Long、Short、Float、Double、Byte、Boolean、Date等),还有为配置添加监听器得接口
⑤ConfigFileChangeListener:
主要实现类是com.ctrip.framework.apollo.internals.PropertiesCompatibleFileConfigRepository
,实现配置文件得同步
⑥internals:
此包包含了所有与服务端交互得逻辑实现。例如RemoteConfigLongPollService
,实现长轮询得处理逻辑,如果服务端返回有配置变更,则会采用事件通知得方式,将配置变更通知到客户端设置得监听器ConfigChangeListner,经过一系列得操作刷新Spring得Environment属性参数(后面会通过源码分析一下,整个调用过程)
⑦model:
此包包含一个配置变更实体对象ConfigChange
,此类中定义了配置变更对应得namespace、变更得属性名propertyName、变更之前得值oldValue、变更之后得值newValue、属性变更类型PropertyChangeType枚举(新增、修改、删除),还有两个事件模型ConfigChangeEvent、ConfigFileChangeEvent
⑧spi:
此包有一些工厂类,如ConfigFactoryManager(用来获取ConfigFactory类,而ConfigFactory类是用来创建Config配置类)、ConfigRegistry类(可以将具体得ConfigFactory配置工厂类注册到某个namespace上)
⑨spring:
此包是动态刷新得关键(后面源码分析会多些篇幅)
apollo-client启动过程源码分析
-
由于Apollo是建立在SpringBoo与SpringCloud之上得配置中心,所以我们分析源码时可以尽量往分析spring boot源码分析那样,这样就会便于记忆了。SpringBoot源码分析可以参考:
SpringBoot启动 源码深度解析(一)
SpringBoot启动 源码深度解析(二)
SpringBoot启动 源码深度解析(三)
SpringBoot启动 源码深度解析(四) -
在SpringBoot项目中,我们使用Apollo得时候都是通过
EnableApolloConfig 通过@Import注解导入ImportBeanDefinitionRegistrar扩展钩子,简直不能再典型得spring方式了(不了解得读者可以参考作者其他文集先把这部分得知识了解一下)。所以,我们接着看com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig
注解来启动apollo-client得相关配置,那么我们就从此注解开始。ApolloConfigRegistrar
类 ApolloConfigRegistrar 此类通过SPI得方式将注册得逻辑委托给ApolloConfigRegistrarHelper
, com.ctrip.framework.apollo.spring.spi.ApolloConfigRegistrarHelper
Apollo默认实现是DefaultApolloConfigRegistrarHelper
,我们可以自定义此处得实现(最好别改,因为那样你会重复做掉宋大佬做的这些事情,而且此处对于实现spring环境属性配置动态刷新得整合基本已经天衣无缝了,不需要再修改什么逻辑)。再看DefaultApolloConfigRegistrarHelper
DefaultApolloConfigRegistrarHelper
① 首先,获取EnableApolloConfig注解对应得value值,即namespace得数组,支持配置多个;再获取order,调用PropertySourcesProcessor
属性资源处理器得addNamespaces方法,将namespace数组添加到PropertySourcesProcessor中得多值map映射成员变量NAMESPACE_NAMES
中(还有一点就是,Apollo中大量使用了Google工具包Guava,方便编程,并且代码看起来也很整齐舒服
) PropertySourcesProcessor
② 判断是否需要注册PropertySourcesPlaceholderConfigurer
占位符处理类,优先级调整为0(此类间接实现了BeanFactoryPostProcessor,所以给其设置优先级,并且优先级越小越先注册,目的就是要在
PropertyPlaceholderConfigurer配置类之前注册,用来解析占位符${}得。因为此处spring版本3.0之前默认是使用PropertyPlaceholderConfigurer,后面得版本使用PropertySourcesPlaceholderConfigurer来代替,并且5.2版本已经不推荐使用了
) PropertyPlaceholderConfigurer
③ 判断是否需要注册PropertySourcesProcessor
处理器,此类是Apollo中用来实现属性动态加载得到关键。实现了BeanFactoryPostProcessor、EnvironmentAware、PriorityOrdered。优先级设置最高。其中bean工厂处理器得回调方法中会执行两个关键性得方法 PropertySourcesProcessor
initializePropertySources方法:
初始化Apollo得名为ApolloPropertySources得属性资源配置,将前面添加到多值映射map中得namespace遍历依次通过ConfigService.getConfig(namespace)代码
来获取配置Config(此处一个api看似很简单,但是其中涉及到得却是整个Apollo客户端与服务端交互得逻辑,后面会单独分析拉取配置代码得逻辑
),然后构造ConfigPropertySource(Apollo中得类,实现了org.springframework.core.env.EnumerablePropertySource)
添加到创建得CompositePropertySource组合配置属性类中,接着将NAMESPACE_NAMES缓存清掉,最后如果spring environment中包含名为ApolloBootstrapPropertySources得启动属性配置,则将上面创建得CompositePropertySource对象添加到其之后,如果没有,则添加到第一位(确保Apollo得属性配置放在第一位) initializePropertySources方法
initializeAutoUpdatePropertiesFeature方法:
创建AutoUpdateConfigChangeListener
监听器,此类实现了配置监听器接口ConfigChangeListener
,是实现诸如@Value注解属性动态刷新得关键
。获取所有namespace对应得ConfigPropertySource,将监听器添加进去实现监听
④ 判断是否需要注册ApolloAnnotationProcessor
处理器,此类会处理Apollo内置得两个重要得注解:ApolloConfig
:用在属性上,注入指定namespace对应得Config;ApolloConfigChangeListener
:用在方法上,用来监听指定得namespace对应得key,原理就是通过创建namespace对应得监听器ConfigChangeListener
,反射调用方法实现配置推送。 com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor#processField处理属性注解ApolloConfig com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor#processMethod处理方法注解ApolloConfigChangeListener
⑤判断是否需要注册SpringValueProcessor
处理器,此类用来处理带有@Value注解得属性字段,并将值通过SpringValueRegistry
注册器添加到注册器缓存map中(上面提到得监听器AutoUpdateConfigChangeListener接收到配置变更时就会通过反射更新缓存中得值
)
⑥判断是否注册SpringValueDefinitionProcessor
处理器,主要是用来处理xml得跟SpringValueProcessor
功能类型
⑦判断是否需要注册ApolloJsonValueProcessor
处理器,用来处理属性注解ApolloJsonValue
得,支持属性值是json格式得,用法同SpringValueProcessor -
至此,通过EnableApolloConfig注解驱动得相关配置类基本分析完毕(
另外,上面提到得spring包下还有很多关于boot得特性使用,如
ApolloApplicationContextInitializer初始化阶段靠前,会创建前面提到得启动属性配置ApolloBootstrapPropertySources,并且实现了EnvironmentPostProcesso可以将优先更靠前,提到ConfigFileApplicationListener之后
)。
关于实时拉取配置ConfigService.getConfig(namespace)代码得源码分析
-
此方法是Apollo提供得统一拉取配置得Entry Point,
com.ctrip.framework.apollo.ConfigService#getConfig
先获取配置管理类ConfigManager,进入到getManager()方法中,通过ApolloInjector.getInstance(ConfigManager.class)这代码以Double check得方式获取对象,其中getInstance方法又会先获取Injector
实例,获取方式是通过Java SPI来实现,而且是获取第一个配置得实现。 ApolloInjector
Apollo也内置了一个实现DefaultInjector
实例, 默认实现DefaultInjector ,所以最终获取ConfigManager实例得地方在DefaultInjector的getInstance方法中 DefaultInjector
如上图无参构造器中会创建一个com.google.inject.Injector
实例,首先通过Guice.createInjector(new ApolloModule())来初始Guice容器,然后从容器中选择对应的实例
,此处是通过Google提供的类似于Spring容器的Guice容器,比较轻量级。ApolloModule类中就是容器中创建的对象,基本以单例为主(更多用法读者需要时自行查阅)。从module中可以发现实现类是DefaultConfigFactory
,所以此处的m_configManager引用对象就是DefaultConfigFactory,紧接着调用DefaultConfigFactory的getConfig方法。 DefaultConfigManager ,构造器中同样的套路来获取ConfigFactoryManager
实例,调用实例的getFactory方法来创建ConfigFactory实例对象,从module中可知ConfigFactoryManager的实例是DefaultConfigFactoryManager,那么会调用DefaultConfigFactoryManager的getFactory方法 com.ctrip.framework.apollo.spi.DefaultConfigFactoryManager#getFactory 可以看到优先从指定的namespace注册器中先获取对应的对象,若没注册,从缓存中获取,再没有从Guice中获取对应名称的配置工厂对象(默认不支持),此处肯定会返回空,最后从Guice容器中获取默认的实例,此处默认是DefaultConfigFactory,所以调用DefaultConfigFactory的create方法 DefaultConfigFactory 此处创建配置Config的时候会有个yaml与yml文件兼容性判断,一般情况下我们创建的namespace都是aaa.bbb.ccc等,所以此处返回的是默认格式properties,会调用createLocalConfigRepository
方法创建配置仓库,createLocalConfigRepository方法中又会判断Apollo环境是否是本地模式,若是本地模式则就不存在远程仓库的概念了,我们此处不使用本地文件模式。则会调用createRemoteConfigRepository
方法创建远程仓库RemoteConfigRepository对象。可以看出一个namespace对应一个远程仓库对象,并且此仓库是实现配置拉取与动态刷新的关键点。 com.ctrip.framework.apollo.spi.DefaultConfigFactory#createRemoteConfigRepository
RemoteConfigRepository:
无参构造器中会赋值成员变量: RemoteConfigRepository构造器 其中比较重要的几个变量或操作有
①ApolloConfig的原子引用
:存储namespace对应的配置信息,包括appId、集群信息、当前namespace下对应的配置属性(Map<String,String>)、发布对应的key,此处起到缓存的作用。
②HttpUtil
:与服务端通信的请求封装(Java原生的http请求)
③ConfigUtil
:包括一些配置的参数,例如:定时刷新间隔refreshInterval属性(默认5分钟)
④ConfigServiceLocator
:获取服务端configService的地址(首先调用metaSerrvice接口,通过服务发现功能(Eureka)获取配置服务端的服务列表)
⑤RemoteConfigLongPollService
:实现客户端长轮询(配置推送),包括接收到变更响应结果之后会发出配置变更事件通知等操作
⑥原子引用m_longPollServiceDto
:当服务端通知过来之后,会设置服务列表第一个值为此结果,即第一个通知到的服务端
⑦原子引用m_remoteMessages
:当长轮询检测到配置变更时会调用原子引用设置变更的通知信息
⑧限流RateLimiter
:每秒两次,此参数是在m_configUtil中配置的
⑨强制刷新原子标志位m_configNeedForceRefresh
:默认为true,什么意思呢? 就是如果Apollo服务端请求失败之后,如果是强制刷新的话,线程sleep1秒钟,接着调用配置服务端,否则使用失败策略的阻塞配置(两个时间实际都是1秒)
⑩trySync方法
:拉取一次配置,方法继承自抽象父类,典型的模板设计模式,实际调用的是sync方法 com.ctrip.framework.apollo.internals.RemoteConfigRepository#sync 获取本地缓存的namespace属性配置信息,接着调用 loadApolloConfig方法获取当前namespace对应的配置信息,loadApolloConfig就是刚才所说的:首先获取配置服务列表;然后随机获取一个服务实例,若有服务端通知到当前客户端则会优先调用该服务端去拉取配置,返回结果。若当前拉取的配置与之前不相同,则会调用fireRepositoryChange方法触发RepositoryChangeListener监听器回调通知, com.ctrip.framework.apollo.internals.AbstractConfigRepository#fireRepositoryChange 我们前面创建的是DefaultConfig,则调用DefaultConfig的实现 com.ctrip.framework.apollo.internals.DefaultConfig#onRepositoryChange ,可以看到方法内会检查配置是否变更,若变更的话会调用父类的fireConfigChange方法 com.ctrip.framework.apollo.internals.AbstractConfig#fireConfigChange 遍历所有添加的监听器,执行其onChange方法,此处就回调到真正实现配置变更的监听器AutoUpdateConfigChangeListener和ApolloAnnotationProcessor(用来处理@ApolloConfigChangeListener注解
) AutoUpdateConfigChangeListener com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor#processMethod
看到这里就可以与我们前面分析的关键类联系起来了。
十一schedulePeriodicRefresh方法
:定时调用trySync方法获取配置信息,每隔5分钟 com.ctrip.framework.apollo.internals.RemoteConfigRepository#schedulePeriodicRefresh
十二scheduleLongPollingRefresh方法
:长轮询获取配置信息。入口是调用RemoteConfigLongPollService的submit方法 RemoteConfigLongPollService com.ctrip.framework.apollo.internals.RemoteConfigLongPollService#doLongPollingRefresh
doLongPollingRefresh方法
:此方法是实现长轮询的核心逻辑,关键性步骤已用红框标出
第①部分:随机选择配置服务的实例信息,构造请求url
第②部分:http请求调用服务端并返回Apollo配置通知列表信息
第③部分:若请求状态为200表示有配置变更,则会调用updateNotifications、updateRemoteNotifications、notify等方法实现配置同步变更操作。
updateNotifications
:将配置通知id存入到ConcurrentMap<String(namespace), Long(通知ID)> m_notifications缓存中
updateRemoteNotifications
:将Apollo配置信息存入到Map<String(namespace), ApolloNotificationMessages(通知信息)> m_remoteNotificationMessages缓存中
notify
:回调远程配置仓库RemoteConfigRepository的onLongPollNotified方法,然后同步拉取配置信息(前面提到过)
第④部分:若返回的状态码是304,表示没有配置变更,则会将选择的服务实例重置为空(又会重新随机选择服务实例进行调用),接着重复执行上述步骤。 -
分析到此处,整个apoll-client的原理及源码已经大致讲解完毕,如果没看过源码的读者可以参考本篇进行源码阅读,也可以帮作者指出勘误或者分析错误之处,愿与君共勉 !
- ☛ 文章要是勘误或者知识点说的不正确,欢迎评论,毕竟这也是作者通过阅读源码获得的知识,难免会有疏忽!
- ☛ 要是感觉文章对你有所帮助,不妨点个关注,或者移驾看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
- ☛ 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处!
网友评论