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
    OrderOrder1>2 谁大谁晚执行
    OrderPriorityOrdered实现Order始终要比实现PriorityOrdered的晚执行
    PriorityOrderedPriorityOrdered1>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环境等操作。