Apollo配置监听源码理解
前言
Apollo配置中心是多个对环境、集群、应用、配置文件、配置项概念划分的数据存储的仓库。通过使用Apollo-client.jar包连接Apollo配置中心读取配置,实时从配置中心获取获取配置变更,自定义注解以及修改Spring Boot自身注解。
所以说:对远程配置仓库的配置读取,持久化,配置变更监听,以及Apollo自己添加的几个Annotation如@ApolloConfig、@ApolloConfigChangeListener等都是在Apollo-client.jar包中实现。
基础介绍
首先下面文章内容涉及的一些技术点先做铺垫及预热。
-
Java6新的特性 ServiceLoader,了解Spi思想 参考
-
采用了Guice的依赖注入而且都是单例 参考
-
关于Order接口与PriorityOrdered接口作用
比如有两个类实现了BeanFactoryPostProcessor接口的postProcessBeanFactory方法,
都能在BeanFactory准备工作完成后做一些定制化的处理,那么就有执行顺序。
实现的接口1 实现的接口2 实现的getOrder()值比较1:2 Order Order 1>2 谁大谁晚执行 Order PriorityOrdered 实现Order始终要比实现PriorityOrdered的晚执行 PriorityOrdered PriorityOrdered 1>2 谁大谁晚执行 Order 不实现 不实现排序的类始终要比实现了Order的列晚执行 PriorityOrdered 不实现 不实现排序的类始终要比实现了PriorityOrdered的列晚执行 不实现 不实现 随机吧
如何实现配置读取
Apollo的配置信息接口Config里面主要含有配置信息和配置更改监听器。
Apollo首先实现了ApplicationContextInitializer与EnvironmentPostProcessor接口,在对应用程序上下文初始化initialize方法与对配置文件读取postProcessEnvironment方法中,都调用了远程读取配置的过程,区别是postProcessEnvironment方法执行的更早,可以在日志系统加载前执行(但这样日志系统就不能输出这时Apollo的远程远程拉取配置过程信息)。通过以上接口都能获取ConfigurableEnvironment当前运行环境从而获取本地application的配置信息,将要读取的配置文件名循环遍历调用ConfigFactory接口获取Config配置实例。
ConfigFactory在创建Config时,首先通过aplication配置项指定路径访问仓库获取所有可用congfigservice服务地址,并创建一个定时获取服务地址的线程。通过调用服务地址拼接应用id、集群名、配置文件名信息的api获取配置文件的所有配置项并持久化磁盘指定位置,并创建一个定时获取配置的线程。
当然,这只是大概的思路,具体细节会更加复杂一些,配置监听器实现,下面说下具体实现细节,建议对着源码来看。
配置读取实现细节
相关类
-
ApolloApplicationContextInitializer 配置初始化的入口
-
ApolloInjector 保持多个类的单例状态
-
Config 配置文件的详细信息数据接口与监听器
-
ConfigFactory 远程拉取配置,持久化配置,定时器设定
逻辑步骤
在ApolloApplicationContextInitializer类内调用 initialize(ConfigurableEnvironment environment) 初始化配置
调用ConfigService类的静态getConfig方法
方法实现:return s_instance.getManager().getConfig(namespace);
讲解:s_instance.getManager() 获取到的是 ConfigManager 的实现类 DefaultConfigManager
其中:ApolloInjector.getInstance(ConfigManager.class) 里面方法实现:通过 Java 的 Spi 技术获得 Injector 的实现类 DefaultInjector 对象,在DefaultInjector 类无参构造中初始化Guice的依赖注入,创建一个注射器。
实现方法:Guice.createInjector(new ApolloModule()); 注入的类、接口的实现类都是Singleton(单例)。
使用注射器获取了 ConfigManager 的实现类 DefaultConfigManager ,并调用了getConfig(namespace) 方法,方法中使用ConfigFactory类creat方法
creat方法主要实现:new DefaultConfig(namespace, createLocalConfigRepository(namespace));
creat方法创建 DefaultConfig 对象时的层次解析
- DefaultConfig:指定配置名的配置文件的详细信息数据
- LocalFileConfigRepository:持久化本地配置存储库
- RemoteConfigRepository:连接远程配置存储库
RemoteConfigRepository初始化
- 在初始化阶段首先获取了 ConfigServiceLocator 类的实例对象(单例)(第一次获取)
ConfigServiceLocator初始化
ConfigServiceLocator初始化主要有两件事
tryUpdateConfigServices() 方法获取了远程服务器的真实连接地址细节:如果是多个远程连接地址会使用random读取
schedulePeriodicRefresh() 方法开启了一个定时器:定时执行第一个方法
- RemoteConfigRepository初始化时主要有三件事
trySync() 方法同步远程仓库获取指定配置名配置信息
this.schedulePeriodicRefresh() 设置定时器,定时执行this.trySync()
this.scheduleLongPollingRefresh() 设置定时器,定时获取仓库配置更新通知
LocalFileConfigRepository初始化
LocalFileConfigRepository初始化主要做了三件事
this.setLocalCacheDir(findLocalCacheDir(), false); 判断当前操作系统等配置项值生成文件夹
this.setUpstreamRepository(upstream); 将自身作为监听器放入RemoteConfigRepository 对象中
this.trySync(); 开始同步,将RemoteConfigRepository 对象中的配置项持久化到指定文件下
DefaultConfig初始化
DefaultConfig初始化主要做了二件事
loadFromResource(m_namespace); 读取本地配置 "META-INF/config/${m_namespace}.properties" 配置文件信息
initialize(); 初始化整理 LocalFileConfigRepository 对象的配置项
配置读取结束
在ApolloApplicationContextInitializer类用 initialize(ConfigurableEnvironment environment) 初始化配置最后一段
environment.getPropertySources().addFirst(composite); //将所有远程配置信息载入运行环境中
配置监听器实现细节
相关类图
蓝色代表普通类,红色代表抽象类,黄色代表接口,冒红光的代表是spring自身的类
实现思路
在 AbstractConfigRepository 抽象类中有个 fireRepositoryChange 方法
遍历执行所有的 RepositoryChangeListener 实现类 onRepositoryChange 实现方法
在 AbstractConfig 抽象类中有个 fireConfigChange 方法
遍历执行所有的 ConfigChangeListener 实现类 onChange 实现方法
在 RemoteConfigRepository 类中定时线程执行同步远程本地配置时,当配置发生与本地配置不一样时执行,且程序启动后这里有两个监听器 LocalFileConfigRepository 类与 DefaultConfig 类,它们的主要触发的事件是:
- LocalFileConfigRepository 类更改本地缓存文件信息
- DefaultConfig 类调用 AbstractConfig 类的 fireConfigChange 方法,执行 ConfigChangeListener 监听方法
配置监听器总结
配置监听主要发生于定时拉取所有配置时,执行相关的监听器,还能动态添加 ConfigChangeListener 监听器。
apollo的注解如何实现
apollo修改的注解代表有三个,@ApolloConfig(用来自动注入Config对象)、@ApolloConfigChangeListene(用来自动注入ConfigChangeListene)、@Value(改为实时监听)。
apollo实现了 BeanPostProcessor 接口,这个接口的作用是对每个Bean初始化前后做操作,在 postProcessBeforeInitialization(bean初始化前的后期处理)。在初始化时会对所有带指定注解的类做相应的逻辑处理,如@ApolloConfig会把一个namespase的所有配置项都拿到,@ApolloConfigChangeListene会对某个namespase设置监听器,其实这个namespase在程序运行中对应的是前文配置读取中的Config类。在前文的RemoteConfigRepository 会将所有获取到的配置信息存放在 ConfigPropertySourceFactory 单例中。而注解都是从这个单例中获取获得信息源再处理
apollo注解实现细节
相关类图
蓝色代表普通类,红色代表抽象类,黄色代表接口,冒红光的代表是spring自身的类
BeanFactory初始化
上面类关系图是注解的入口,通过1起点开始初始化,在1.5、2、3个类都实现了BeanFactoryPostProcessor 类的 postProcessBeanFactory 方法,用于修改应用程序上下文的内部bean工厂,其中1.5接口 postProcessBeanDefinitionRegistry 方法优先级最高,所以1.5的实现类 ConfigPropertySourcesProcessor 类先执行,然后再2类,因为他实现了 PriorityOrdered 接口,最后3类执行。
1.5实现类方法实现在spring中注入了多个bean初始化处理类,并将spring自身的 PropertySourcesPlaceholderConfigurer Bean类重新注册将order值调到0,时该类的bean处理过程先执行,然后在执行apollo对@Value 的bean处理器。
2类方法实现从 ConfigPropertySourceFactory 单例中取出所有数据源,并调整配置项的优先级如 bootstrap 配置项的配置文件应该是优先级最高的,并如果开启@Value的实时更新,会在 ConfigPropertySourceFactory 单例的数据源集合循环添加 ConfigChangeListener 监听器。
这个监听器的实现类是 AutoUpdateConfigChangeListener 类,该类中的 springValueRegistry 属性是一个单例 springValueRegistry 对象
3类方法实现如果开启@Value的实时更新,获取bean定义注册表时记录的所有bean的信息,注册表信息记录在 SpringValueDefinitionProcessor 类(上面没有)。
@ApolloConfig实现细节
在1.5类注入 ApolloAnnotationProcessor 类对bean初始化时从 DefaultConfigManager 单例中取对应的Config对象注入
@ApolloConfigChangeListene实现细节
在1.5类注入 ApolloAnnotationProcessor 类对bean初始化时从 DefaultConfigManager 单例中取对应的Config对象,new 一个 ConfigChangeListener 监听器放入 Config 对象里面最后交由上面说过的定时器执行。
@Value实时同步实现细节
在1.5类注入 SpringValueProcessor 类对bean处理时会处理所有带@Value的类,并将它们放入 springValueRegistry 对象的 Map中,而 springValueRegistry 对象是单例的,前文 BeanFactory 初始化时2类中有对单例的 ConfigPropertySourceFactory 数据源进行添加监视器,而这监视器里面就包含了一个 springValueRegistry 对象,所有当定时器同步数据时,发现改变一路执行过来的触发器就会执行到它,从而使SpringValueRegistry内部的值也达到最新
总结
在这次的源码分析过程中,可以大体的看到apollo配置中心关于客户端的实现过程及思路。以及一个 java 项目如何和spring框架融合 ---- 注册,初始化,注入spring环境等操作。