一、常用使用方式和功能:
- apollo引入方式:
- Java配置:@EnableApolloConfig
- spring boot:apollo.bootstrap.enabled = true
- 常见用法:
- @Value自动更新
- @ConfigurationProperties + @RefreshScop
- @ApolloConfig注入Config对象手动获取
- 配置文件占位符自动替换如,name = ${xx}
- 配置完全托管给apollo
二、Spring周期和Apollo相关扩展点:
- EnvironmentPostProcessor:Spring收集配置(系统变量、环境变量等)的后置扩展,用于处理Environment,使用里面的PropertySource进行配置存取,这时还没有开始bean相关的操作,Apollo在这个阶段提前把从远程获取到的配置放进PropertySource,完成第一步的配置初始化
- ImportBeanDefinitionRegistrar:Spring解析并生成BeanDefinition对象阶段,Apollo在这个阶段加入了动态更新等处理器,Client主要看的也是这部分
- BeanFactoryPostProcessor:bean工厂后置扩展
- Bean实例化,调用构造函数等
- BeanPostProcessor:Bean初始化前处理器
- Bean初始化
- BeanPostProcessor:Bean初始化后处理器
- 销毁(略)
三、代码部分:
-
官方对Client流程的整体描述,接Config Service长轮询部分
- Config、ConfigRepository:Apollo对配置和仓库等概念的抽象
-
ConfigRepository:抽象了配置仓库。包括远程仓库(从Config Service拿到的数据)、本地仓库(本地文件缓存)
image - LocalFileConfigRepository:代表本地文件配置库,获取远程仓库配置时使用远程配置,更新时同时同步本地缓存文件,本地配置算远程配置的降级方案,在源码中远程仓库是本地仓库的“上游”
- RemoteConfigRepository:代表远程仓库。配置查询上两种机制并行,一是定时任务5分钟一次从config service查询配置,二是长轮询,如下
- RemoteConfigLongPollService:实现长轮询请求的类,之前看到了Config Service里的服务端,Config Service有更新就会返回200,但更新数据不会返回配置,只能知道哪个namespace,根据namespace再去查询更新,RemoteConfigRepository则继续长轮询。如果超时返回304后也是继续轮询,客户端超时90s,服务端超时60s。
- Config:代表具体配置,这块很杂有兴趣可以自行查看代码
- 启动方式apollo.bootstrap.enabled和@EnableApolloConfig:
- apollo.bootstrap.enabled是基于SpringBoot配置文件的
- @EnableApolloConfig是基于JavaConfig的,这种包含了SpringBoot方式所有逻辑还附加了逻辑,所以以这种为主轴看代码,和上一种的差异原因也会写出来
- @EnableApolloConfig注解:
- 注解里@Import(ApolloConfigRegistrar.class),ApolloConfigRegistrar类实现了ImportBeanDefinitionRegistrar,执行在创建Bean之前收集BeanDefinition阶段,是很靠前的一个扩展点,Apollo加入了几个自定义Bean,下面按执行Bean主逻辑执行时间线进行分析
- 准备阶段ApolloApplicationContextInitializer:
- 通过spring.factories扫描加载,实现了两个接口ApplicationContextInitializer和EnvironmentPostProcessor所以有两个阶段逻辑
- EnvironmentPostProcessor#postProcessEnvironment():①
- 这里有个配置apollo.bootstrap.eagerLoad.enabled,可以提前在这个阶段(日志系统加载前)执行ApplicationContextInitializer#initialize()逻辑,加载Apollo远程配置
- ApplicationContextInitializer#initialize():②
- 这里初始化了environment的ApolloBootstrapPropertySources,里面的配置从ConfigRepository(Config Service)拿到的,这时Bootstrap数据源就有数据了,后面在PropertySourcesProcessor中会用到
- DefaultApolloConfigRegistrarHelper加入Apollo自定义Bean:③
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取所有@EnableApolloConfig注解和参数
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(EnableApolloConfig.class.getName()));
final String[] namespaces = attributes.getStringArray("value");
final int order = attributes.getNumber("order");
// 解析namespaces里的占位符并添加到PropertySourcesProcessor
final String[] resolvedNamespaces = this.resolveNamespaces(namespaces);
PropertySourcesProcessor.addNamespaces(Lists.newArrayList(resolvedNamespaces), order);
// 配置PropertySourcesPlaceholderConfigurer优先级
Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
propertySourcesPlaceholderPropertyValues.put("order", 0);
// 处理配置里的占位符(BeanFactoryPostProcessor)
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
// 更新@Value实现不用@RefreshScope刷新
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, AutoUpdateConfigChangeListener.class);
// 处理@EnableApolloConfig的namespace,(配置文件启动方式里没有这个注解,所以去掉了这部分)
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class);
// 处理@ApolloConfig、@ApolloJsonValue、@ApolloConfigChangeListener并注入相关信息
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class);
// 收集@Value和xml的配置信息放入SpringValueRegistry
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class);
// 把xml中的配置信息提取出来key 占位符等等并存储,在SpringValueProcessor作出处理
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class);
}
- SpringValueDefinitionProcessor:④
- 实现BeanDefinitionRegistryPostProcessor,也是处理BeanDefinition阶段,用于处理xml配置占位符
- 用双层map保存了BeanDefinitionRegistry、beanName、SpringValueDefinition的映射关系,SpringValueDefinition里存了xml有哪些占位符和相关替换的信息,后续SpringValueProcessor会用到
- PropertySourcesProcessor:⑤
- 实现了BeanFactoryPostProcessor,在之前registerBeanDefinitions()中有个和本类相关的逻辑,把@EnableApolloConfig的namespaces和order属性加入到了本类,所以大概能知道这个类是处理注解下namespaces的,接ApolloApplicationContextInitializer部分
- Client启动时用户指定了app、env、cluster能定位项目的具体配置,ApolloBootstrapPropertySources里存了定位下的每个namespace,每个namespace都被算做一个配置数据源,数据源下面的source属性代表对应的一组配置项,除此之外还有一个特殊的源ApolloPropertySources代表@EnableApolloConfig标记的数据源的就是在这处理的
- 主要两个逻辑
- 初始化数据源:这里把ApolloBootstrapPropertySources的优先级放到了第一,第二位就是ApolloPropertySources,这样优先使用ApolloBootstrapPropertySources
- 添加监听:数据源里的每个configPropertySource都添加了修改配置时的回调,回调触发ApolloConfigChangeEvent,AutoUpdateConfigChangeListener逻辑最后说
- PropertySourcesPlaceholderConfigurer:⑥
- 本身是BeanFactoryPostProcessor,注册后会在BeanFactory后置阶段执行,用于对XML和@Value里的“${xx}”占位符进行解析,配置来源于environment
- SpringValueProcessor:
- 实现了BeanPostProcessor和BeanFactoryPostProcessor,所以有Bean初始化before和BeanFactory阶段两个扩展逻辑,主要用于@Value和xml配置的收集并注册到SpringValueRegistry
- BeanFactoryPostProcessor#postProcessBeanFactory:⑦
- 把之前SpringValueDefinitionProcessor里获取到的xml SpringValue取出备用
- BeanPostProcessor#postProcessBeforeInitialization:⑨
- 首先收集@Value相关信息组装成SpringValue注册到SpringValueRegistry,然后处理xml的SpringValue,同样流程
- ApolloAnnotationProcessor:⑧
- 是一个BeanPostProcessor,这里主要在before阶段处理四个注解@ApolloConfig、@ApolloJsonValue、@ApolloConfigChangeListener
- @ApolloConfig:用在字段上,可根据namespace注入Config对象,config对象携带了配置信息
- @ApolloConfigChangeListener:用在方法上,可根据namespace监听配置变更事件并传入ConfigChangeEvent携带了配置变更信息
- @ApolloJsonValue:用在字段和方法上,把JSON字符串转成对象注入
- 知道了注解含义后处理就比较简单了,@ApolloConfig直接从ConfigService获取了Config,赋值给字段,Config根本上都是从上面提到Configrepository获取,@ApolloConfigChangeListener逻辑是给Config添加了一个监听回调,回调时用invokeMethod把配置相关信息写进去,@ApolloJsonValue类似有兴趣可以看看
- AutoUpdateConfigChangeListener:配置更新时@Value实现自动更新的Bean!⑩
- 一共两个监听,ConfigChangeListener、事件ApolloConfigChangeEvent,最后都是调用onChange(),onChange()负责触发配置修改时把@Value的字段set成新值,完成配置自动更新
- onChange()第一步先获取到所有需要修改的key
- 从之前收集xml和@Value的SpringValueRegistry里查看是否存在,之前提到SpringValueRegistry缓存了所有@Value、xml标注的字段、方法,不存在说明不需要Apollo控制更新跳过
- PS:SpringValue代表某个@Value的所有信息。包括Key、占位符格式如"${ccc:0}、所属Bean、对应的字段Field等
- 更新流程:利用PlaceholderHelper从environment解析出占位符的value,并通过SpringValue里的反射方法更新对应字段value,到这里@Value就是新值了,所以总结来说还是用的反射完成的自动更新
public class PlaceholderHelper {
...
/**
* 把占位符解析成具体值
*/
public Object resolvePropertyValue(ConfigurableBeanFactory beanFactory, String beanName, String placeholder) {
// 获取解析的值
String strVal = beanFactory.resolveEmbeddedValue(placeholder);
BeanDefinition bd = (beanFactory.containsBean(beanName) ? beanFactory
.getMergedBeanDefinition(beanName) : null);
// resolve expressions like "#{systemProperties.myProp}"
return evaluateBeanDefinitionString(beanFactory, strVal, bd);
}
...
}
public class SpringValue {
......
// field.set()给字段设置新值
private void injectField(Object newVal) throws IllegalAccessException {
Object bean = beanRef.get();
if (bean == null) {
return;
}
boolean accessible = field.isAccessible();
field.setAccessible(true);
field.set(bean, newVal);
field.setAccessible(accessible);
}
// 用方法设置新值
private void injectMethod(Object newVal)
throws InvocationTargetException, IllegalAccessException {
Object bean = beanRef.get();
if (bean == null) {
return;
}
methodParameter.getMethod().invoke(bean, newVal);
}
......
}
- 总结:Clinet部分主要了解一是对配置,包括远程、本地的抽象。二是Apollo在Spring的关键扩展点做的处理;最后就是长轮训Client怎么和meta service、config service通信
源码里相关的知识点:
-
Environment和PropertySource:https://www.codenong.com/jsf6f51ff3e0f2/
-
总结:Environment包括Java环境变量、系统环境变量等。PropertySource代表数据源,以kv形式使用配置,Environment里加入了各种源的PropertySource直接使用,也可以自定义配置数据源到Environment。apollo里所有namespace都放在了Environment下的PropertySource,PropertySource下的source获取具体配置kv
-
@Value:https://blog.csdn.net/weixin_46228112/article/details/124623568
-
总结:@Value数据来源于PropertySource或者间接说是Environment,但改变PropertySource并不会改变运行时的值,有两种方式一是@RefreshScope刷新bean,二是apollo里用反射直接设置
-
@ConfigurationProperties:https://blog.csdn.net/qq_31854907/article/details/121470769
-
总结:bean后置处理器,和@Value类似都是从PropertySource获取,然后绑定到bean里
-
@RefreshScope:https://blog.csdn.net/luqiang81191293/article/details/106678065
-
总结:使用@Scope做了代理,正常使用时会使用RefreshScope父类里缓存的Bean,可执行清理,清理后会重新创建Bean达到刷新的目的,apollo里@ConfigurationProperties需要用户添加@RefreshScope+调用refresh()才能实现刷新
-
ImportBeanDefinitionRegistrar:使用ImportBeanDefinitionRegistrar动态创建自定义Bean到Spring中
-
总结:在spring周期中用来处理自定义BeanDefinition,调用在Bean初始化之前,Apollo中在这个扩展点中加入的配置注册、变更监听等相关逻辑
-
multimap:Java使用multimap数据结构_srping123的博客-CSDN博客_java multimap
网友评论