# 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 环境等操作。