# 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) 初始化配置

image-20201205150123152

​ 调用 ConfigService 类的静态 getConfig 方法

方法实现:return s_instance.getManager ().getConfig (namespace);

讲解:s_instance.getManager () 获取到的是 ConfigManager 的实现类 DefaultConfigManager

image-20201205150403715

​ 其中:ApolloInjector.getInstance (ConfigManager.class) 里面方法实现:通过 Java 的 Spi 技术获得 Injector 的实现类 DefaultInjector 对象,在 DefaultInjector 类无参构造中初始化 Guice 的依赖注入,创建一个注射器。

​ 实现方法:Guice.createInjector (new ApolloModule ()); 注入的类、接口的实现类都是 Singleton (单例)。

image-20201205150524503

​ 使用注射器获取了 ConfigManager 的实现类 DefaultConfigManager ,并调用了 getConfig (namespace) 方法,方法中使用 ConfigFactory 类 creat 方法

creat 方法主要实现:new DefaultConfig (namespace, createLocalConfigRepository (namespace));

​ creat 方法创建 DefaultConfig 对象时的层次解析

- DefaultConfig:指定配置名的配置文件的详细信息数据
- LocalFileConfigRepository:持久化本地配置存储库
- RemoteConfigRepository:连接远程配置存储库

# RemoteConfigRepository 初始化

image-20201205151738504

  1. 在初始化阶段首先获取了 ConfigServiceLocator 类的实例对象 (单例)(第一次获取)
# ConfigServiceLocator 初始化

ConfigServiceLocator 初始化主要有两件事

tryUpdateConfigServices () 方法获取了远程服务器的真实连接地址细节:如果是多个远程连接地址会使用 random 读取

schedulePeriodicRefresh () 方法开启了一个定时器:定时执行第一个方法

image-20201205152101057

  1. RemoteConfigRepository 初始化时主要有三件事

trySync () 方法同步远程仓库获取指定配置名配置信息

this.schedulePeriodicRefresh () 设置定时器,定时执行 this.trySync ()

this.scheduleLongPollingRefresh () 设置定时器,定时获取仓库配置更新通知

# LocalFileConfigRepository 初始化

image-20201205153055540

LocalFileConfigRepository 初始化主要做了三件事

this.setLocalCacheDir (findLocalCacheDir (), false); 判断当前操作系统等配置项值生成文件夹

this.setUpstreamRepository (upstream); 将自身作为监听器放入 RemoteConfigRepository 对象中

this.trySync (); 开始同步,将 RemoteConfigRepository 对象中的配置项持久化到指定文件下

# DefaultConfig 初始化

image-20201205155437512

DefaultConfig 初始化主要做了二件事

loadFromResource (m_namespace); 读取本地配置 “META-INF/config/${m_namespace}.properties” 配置文件信息

initialize (); 初始化整理 LocalFileConfigRepository 对象的配置项

# 配置读取结束

​ 在 ApolloApplicationContextInitializer 类用 initialize (ConfigurableEnvironment environment) 初始化配置最后一段

environment.getPropertySources ().addFirst (composite); // 将所有远程配置信息载入运行环境中

# 配置监听器实现细节

# 相关类图

蓝色代表普通类,红色代表抽象类,黄色代表接口,冒红光的代表是 spring 自身的类

img

# 实现思路

​ 在 AbstractConfigRepository 抽象类中有个 fireRepositoryChange 方法

遍历执行所有的 RepositoryChangeListener 实现类 onRepositoryChange 实现方法

image-20201205185447159

​ 在 AbstractConfig 抽象类中有个 fireConfigChange 方法

遍历执行所有的 ConfigChangeListener 实现类 onChange 实现方法

image-20201205191117912

​ 在 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 自身的类

image-20201208102151265

image-20201208111822587

# 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 处理器。

image-20201208104922527

​ 2 类方法实现从 ConfigPropertySourceFactory 单例中取出所有数据源,并调整配置项的优先级如 bootstrap 配置项的配置文件应该是优先级最高的,并如果开启 @Value 的实时更新,会在 ConfigPropertySourceFactory 单例的数据源集合循环添加 ConfigChangeListener 监听器。

这个监听器的实现类是 AutoUpdateConfigChangeListener 类,该类中的 springValueRegistry 属性是一个单例 springValueRegistry 对象

image-20201208105542263

​ 3 类方法实现如果开启 @Value 的实时更新,获取 bean 定义注册表时记录的所有 bean 的信息,注册表信息记录在 SpringValueDefinitionProcessor 类 (上面没有)。

# @ApolloConfig 实现细节

​ 在 1.5 类注入 ApolloAnnotationProcessor 类对 bean 初始化时从 DefaultConfigManager 单例中取对应的 Config 对象注入

image-20201208133237127

# @ApolloConfigChangeListene 实现细节

​ 在 1.5 类注入 ApolloAnnotationProcessor 类对 bean 初始化时从 DefaultConfigManager 单例中取对应的 Config 对象,new 一个 ConfigChangeListener 监听器放入 Config 对象里面最后交由上面说过的定时器执行。

image-20201208133605448

# @Value 实时同步实现细节

​ 在 1.5 类注入 SpringValueProcessor 类对 bean 处理时会处理所有带 @Value 的类,并将它们放入 springValueRegistry 对象的 Map 中,而 springValueRegistry 对象是单例的,前文 BeanFactory 初始化时 2 类中有对单例的 ConfigPropertySourceFactory 数据源进行添加监视器,而这监视器里面就包含了一个 springValueRegistry 对象,所有当定时器同步数据时,发现改变一路执行过来的触发器就会执行到它,从而使 SpringValueRegistry 内部的值也达到最新

image-20201208140446334

# 总结

​ 在这次的源码分析过程中,可以大体的看到 apollo 配置中心关于客户端的实现过程及思路。以及一个 java 项目如何和 spring 框架融合 ---- 注册,初始化,注入 spring 环境等操作。