前言
在上一篇文章 Apollo 客户端集成 SpringBoot 的源码分析(1)- 启动时配置获取 中,笔者分析了 Apollo 客户端拉取远端配置并将其嵌入 SpringBoot 的主要流程,本文主要分析 Apollo 配置属性注入 SpringBoot 框架,真正生效的原理
1. 客户端配置属性注入 SpringBoot 源码分析
Apollo 配置属性注入到 SpringBoot 中 Bean 的大致过程也可以分为两个步骤
- Apollo 组件的注入
- Apollo 配置属性的更新
1.1 Apollo 组件的注入
-
在 SpringBoot 中使用 Apollo 客户端一般都需要启用
@EnableApolloConfig
注解,在 SpringBoot 注解 @Import 的原理-ConfigurationClassPostProcessor 源码解析 一文中笔者介绍过@Import
注解的背后原理,可以看到 Apollo 的@EnableApolloConfig
注解正是借助@Import
引入了ApolloConfigRegistrar
@Import(ApolloConfigRegistrar.class) public @interface EnableApolloConfig { String[] value() default {ConfigConsts.NAMESPACE_APPLICATION}; int order() default Ordered.LOWEST_PRECEDENCE; }
-
ApolloConfigRegistrar
源码如下,这个类是注册 Apollo 组件的入口,关键方法如下:ApolloConfigRegistrar#setEnvironment()
方法会在实例创建的过程中被ParserStrategyUtils.invokeAwareMethods()
回调,可以看到这个方法的逻辑非常简单,就是将 Environment 环境保存到DefaultApolloConfigRegistrarHelper
对象中ApolloConfigRegistrar#registerBeanDefinitions()
方法会在 Spring 解析完 BeanDefintion 将其加载进容器的准备阶段回调,这个方法其实也只是个入口,真正完成 Apollo 组件注册的逻辑在DefaultApolloConfigRegistrarHelper#registerBeanDefinitions()
方法中
public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware { private final ApolloConfigRegistrarHelper helper = ServiceBootstrap.loadPrimary(ApolloConfigRegistrarHelper.class); @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { helper.registerBeanDefinitions(importingClassMetadata, registry); } @Override public void setEnvironment(Environment environment) { this.helper.setEnvironment(environment); } }
-
DefaultApolloConfigRegistrarHelper#registerBeanDefinitions()
方法的处理可以分为以下几个步骤:- 解析
@EnableApolloConfig
注解,将注解中配置的字符串数组处理为 namespace 数组,并调用PropertySourcesProcessor.addNamespaces()
方法将其缓存起来 - 调用工具类
BeanRegistrationUtil.registerBeanDefinitionIfNotExists()
方法尝试将 Apollo 组件类包装为 BeanDefintion 并注册到容器中。可以看到注册的 Apollo 关键组件有PropertySourcesProcessor(用于拉取 @EnableApolloConfig 配置的 namespace 的远程配置)
,ApolloAnnotationProcessor(用于处理 Apollo 的专用注解)
,SpringValueProcessor(用于处理 @Value 注解标注的类成员变量和对象方法)
,SpringValueDefinitionProcessor(用于处理 XML 文件中的占位符)
。需注意组件对象的实例化有优先级顺序,getOrder() 返回的值越小优先级越高,对象创建的时机也就越靠前
PropertySourcesPlaceholderConfigurer
是 SpringBoot 框架自身的占位符处理配置,占位符的处理主要是将${apollo.value}
这样的字符串解析出 关键字apollo.value
,再使用这个 key 通过PropertySourcesPropertyResolver
从PropertySource
中找到对应的属性值替换掉占位符@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes attributes = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(EnableApolloConfig.class.getName())); final String[] namespaces = attributes.getStringArray("value"); final int order = attributes.getNumber("order"); final String[] resolvedNamespaces = this.resolveNamespaces(namespaces); PropertySourcesProcessor.addNamespaces(Lists.newArrayList(resolvedNamespaces), order); Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>(); // to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer propertySourcesPlaceholderPropertyValues.put("order", 0); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(), PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(), PropertySourcesProcessor.class); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(), ApolloAnnotationProcessor.class); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(), SpringValueDefinitionProcessor.class); } private String[] resolveNamespaces(String[] namespaces) { String[] resolvedNamespaces = new String[namespaces.length]; for (int i = 0; i < namespaces.length; i++) { // throw IllegalArgumentException if given text is null or if any placeholders are unresolvable resolvedNamespaces[i] = this.environment.resolveRequiredPlaceholders(namespaces[i]); } return resolvedNamespaces; }
- 解析
-
PropertySourcesProcessor
是 Apollo 最关键的组件之一,并且其实例化优先级也是最高的,PropertySourcesProcessor#postProcessBeanFactory()
会在该类实例化的时候被回调,该方法的处理如下:- 调用
PropertySourcesProcessor#initializePropertySources()
拉取远程 namespace 配置 - 调用
PropertySourcesProcessor#initializeAutoUpdatePropertiesFeature()
给所有缓存在本地的 Config 配置添加监听器
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { initializePropertySources(); initializeAutoUpdatePropertiesFeature(beanFactory); }
- 调用
-
PropertySourcesProcessor#initializePropertySources()
方法的处理如下,可以看到和 Apollo 客户端集成 SpringBoot 的源码分析(1)- 启动时配置获取 提到的拉取配置的逻辑基本一致- 调用
ConfigService#getConfig()
依次拉取各个命名空间的远端配置 - 调用
ConfigPropertySourceFactory#getConfigPropertySource()
缓存从远端拉取的配置,并将其包装为PropertySource
,最终将所有拉取到的远端配置聚合到一个以ApolloPropertySources
为 key 的属性源包装类CompositePropertySource
的内部 - 将
CompositePropertySource
属性源包装类添加到 Spring 的 Environment 环境中,此处要保证 Bootstrap 的 namespace 配置如存在的话必须在属性源列表头部
private void initializePropertySources() { if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME)) { //already initialized return; } CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME); //sort by order asc ImmutableSortedSet<Integer> orders = ImmutableSortedSet.copyOf(NAMESPACE_NAMES.keySet()); Iterator<Integer> iterator = orders.iterator(); while (iterator.hasNext()) { int order = iterator.next(); for (String namespace : NAMESPACE_NAMES.get(order)) { Config config = ConfigService.getConfig(namespace); composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config)); } } // clean up NAMESPACE_NAMES.clear(); // add after the bootstrap property source or to the first if (environment.getPropertySources() .contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) { // ensure ApolloBootstrapPropertySources is still the first ensureBootstrapPropertyPrecedence(environment); environment.getPropertySources() .addAfter(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, composite); } else { environment.getPropertySources().addFirst(composite); } }
- 调用
-
PropertySourcesProcessor#initializeAutoUpdatePropertiesFeature()
方法主要的处理是为所有的 Apollo 配置对象添加自动更新的监听器private void initializeAutoUpdatePropertiesFeature(ConfigurableListableBeanFactory beanFactory) { if (!configUtil.isAutoUpdateInjectedSpringPropertiesEnabled() || !AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES.add(beanFactory)) { return; } AutoUpdateConfigChangeListener autoUpdateConfigChangeListener = new AutoUpdateConfigChangeListener( environment, beanFactory); List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources(); for (ConfigPropertySource configPropertySource : configPropertySources) { configPropertySource.addChangeListener(autoUpdateConfigChangeListener); } }
-
ConfigPropertySource#addChangeListener()
方法如下,在 Apollo 客户端集成 SpringBoot 的源码分析(1)- 启动时配置获取 中分析过 ConfigPropertySource 包装类,我们知道这里的this.source.addChangeListener(listener)
实际调用的是DefaultConfig#addChangeListener()
方法public void addChangeListener(ConfigChangeListener listener) { this.source.addChangeListener(listener); }
-
DefaultConfig#addChangeListener()
方法的实现在其父类中AbstractConfig
,实际只是完成将监听器添加到内部监听器列表的动作@Override public void addChangeListener(ConfigChangeListener listener) { addChangeListener(listener, null); } @Override public void addChangeListener(ConfigChangeListener listener, Set<String> interestedKeys) { addChangeListener(listener, interestedKeys, null); } @Override public void addChangeListener(ConfigChangeListener listener, Set<String> interestedKeys, Set<String> interestedKeyPrefixes) { if (!m_listeners.contains(listener)) { m_listeners.add(listener); if (interestedKeys != null && !interestedKeys.isEmpty()) { m_interestedKeys.put(listener, Sets.newHashSet(interestedKeys)); } if (interestedKeyPrefixes != null && !interestedKeyPrefixes.isEmpty()) { m_interestedKeyPrefixes.put(listener, Sets.newHashSet(interestedKeyPrefixes)); } } }
-
回到步骤3,另一个 Apollo 关键组件是
SpringValueProcessor
,这个BeanPostProcessor
后置处理器会对每一个 Bean 对象进行初始化前的处理,也就是回调其SpringValueProcessor#postProcessBeforeInitialization()
方法。这个方法处理很简单,流程的控制其实在其父类ApolloProcessor#postProcessBeforeInitialization()
方法中这一步骤的逻辑是将被
@Value
标注的 Bean 字段缓存下来,如后续这个字段对应的 Apollo 配置有更新,则可以通过反射将更新后的值更新为字段值@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) { super.postProcessBeforeInitialization(bean, beanName); processBeanPropertyValues(bean, beanName); } return bean; }
-
ApolloProcessor#postProcessBeforeInitialization()
方法的逻辑非常清晰,主要分为如下两步:- 对于待处理的 bean 对象,首先调用
ApolloProcessor#findAllField()
将其定义的所有字段都通过反射找出来,然后依次调用抽象方法processField()
处理,也就是实际的字段处理逻辑在子类中 - 接着调用
ApolloProcessor#findAllMethod()
将其定义的所有方法都通过反射找出来,然后依次调用抽象方法processMethod()
处理,同理,实际的处理逻辑在子类中
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { Class clazz = bean.getClass(); for (Field field : findAllField(clazz)) { processField(bean, beanName, field); } for (Method method : findAllMethod(clazz)) { processMethod(bean, beanName, method); } return bean; }
- 对于待处理的 bean 对象,首先调用
-
ApolloProcessor#processField()/ApolloProcessor#processMethod()
在子类SpringValueProcessor
相关实现如下,至此 Apollo 组件注入的分析暂告段落,其余的组件不一一分析了SpringValueProcessor#processField()
方法首先检查字段上是否有@Value
注解,有的话调用placeholderHelper.extractPlaceholderKeys()
方法将注解占位符字符串中的属性 key 解析出来,对于每一个 key,将其和 Field 字段等关键信息封装为SpringValue
对象,并通过SpringValueRegistry#register()
缓存起来SpringValueProcessor#processMethod()
方法的处理与SpringValueProcessor#processField()
基本一致,不再赘述
SpringValueRegistry
内部其实就是个 Map 缓存,读者如有兴趣的话可自行研究@Override protected void processField(Object bean, String beanName, Field field) { // register @Value on field Value value = field.getAnnotation(Value.class); if (value == null) { return; } Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value()); if (keys.isEmpty()) { return; } for (String key : keys) { SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false); springValueRegistry.register(beanFactory, key, springValue); logger.debug("Monitoring {}", springValue); } } @Override protected void processMethod(Object bean, String beanName, Method method) { //register @Value on method Value value = method.getAnnotation(Value.class); if (value == null) { return; } //skip Configuration bean methods if (method.getAnnotation(Bean.class) != null) { return; } if (method.getParameterTypes().length != 1) { logger.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters", bean.getClass().getName(), method.getName(), method.getParameterTypes().length); return; } Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value()); if (keys.isEmpty()) { return; } for (String key : keys) { SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false); springValueRegistry.register(beanFactory, key, springValue); logger.info("Monitoring {}", springValue); } }
1.2 Apollo 配置属性的更新
在 Apollo 客户端集成 SpringBoot 的源码分析(1)- 启动时配置获取 中笔者提到了配置变化的传播路径,本文 1.2 节步骤7又分析了配置变化监听器的设置,可以知道对应于每一个 Config 对象,其实都是默认设置了一个自动更新的监听器的,故本节从 DefaultConfig#onRepositoryChange()
监听到配置仓库变化开始分析
-
DefaultConfig#onRepositoryChange()
方法的处理主要分为了两个步骤:- 调用
DefaultConfig#updateAndCalcConfigChanges()
方法更新配置属性,并计算出实际变更的属性,此处不作详细分析 - 调用继承的
AbstractConfig#fireConfigChange()
方法,将配置变化传播出去,也就是通知监听器
@Override public synchronized void onRepositoryChange(String namespace, Properties newProperties) { if (newProperties.equals(m_configProperties.get())) { return; } ConfigSourceType sourceType = m_configRepository.getSourceType(); Properties newConfigProperties = propertiesFactory.getPropertiesInstance(); newConfigProperties.putAll(newProperties); Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties, sourceType); //check double checked result if (actualChanges.isEmpty()) { return; } this.fireConfigChange(new ConfigChangeEvent(m_namespace, actualChanges)); Tracer.logEvent("Apollo.Client.ConfigChanges", m_namespace); }
- 调用
-
AbstractConfig#fireConfigChange()
方法如下,没有太多逻辑,就是遍历监听器列表,使用线程池任务回调监听器方法而已protected void fireConfigChange(final ConfigChangeEvent changeEvent) { for (final ConfigChangeListener listener : m_listeners) { // check whether the listener is interested in this change event if (!isConfigChangeListenerInterested(listener, changeEvent)) { continue; } m_executorService.submit(new Runnable() { @Override public void run() { String listenerName = listener.getClass().getName(); Transaction transaction = Tracer.newTransaction("Apollo.ConfigChangeListener", listenerName); try { listener.onChange(changeEvent); transaction.setStatus(Transaction.SUCCESS); } catch (Throwable ex) { transaction.setStatus(ex); Tracer.logError(ex); logger.error("Failed to invoke config change listener {}", listenerName, ex); } finally { transaction.complete(); } } }); } }
-
此时
AutoUpdateConfigChangeListener#onChange()
方法将会被调用,可以看到此处用到了本文 1.2 节步骤11 的注册器,从配置变化事件ConfigChangeEvent
取出发生了变化的 key,再检查是否属于当前 BeanFactory,检查通过则调用AutoUpdateConfigChangeListener#updateSpringValue()
更新@Value
标注的字段/方法值public void onChange(ConfigChangeEvent changeEvent) { Set<String> keys = changeEvent.changedKeys(); if (CollectionUtils.isEmpty(keys)) { return; } for (String key : keys) { // 1. check whether the changed key is relevant Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key); if (targetValues == null || targetValues.isEmpty()) { continue; } // 2. update the value for (SpringValue val : targetValues) { updateSpringValue(val); } } }
-
AutoUpdateConfigChangeListener#updateSpringValue()
方法的处理分为两个动作- 首先调用
AutoUpdateConfigChangeListener#resolvePropertyValue()
方法借助 SpringBoot 的组件将@Value
中配置的占位符替换为 PropertySource 中的对应 key 的属性值,此处涉及到 Spring 创建 Bean 对象时的属性注入机制,比较复杂,暂不作深入分析 - 调用
SpringValue#update()
方法实际完成属性值的更新
private void updateSpringValue(SpringValue springValue) { try { Object value = resolvePropertyValue(springValue); springValue.update(value); logger.info("Auto update apollo changed value successfully, new value: {}, {}", value, springValue); } catch (Throwable ex) { logger.error("Auto update apollo changed value failed, {}", springValue.toString(), ex); } }
- 首先调用
-
SpringValue#update()
方法其实就是使用反射机制运行时修改 Bean 对象中的成员变量,至此自动更新完成public void update(Object newVal) throws IllegalAccessException, InvocationTargetException { if (isField()) { injectField(newVal); } else { injectMethod(newVal); } } 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); }