Apollo 客户端集成 SpringBoot 的源码分析(2)-配置属性的注入更新

前言

在上一篇文章 Apollo 客户端集成 SpringBoot 的源码分析(1)- 启动时配置获取 中,笔者分析了 Apollo 客户端拉取远端配置并将其嵌入 SpringBoot 的主要流程,本文主要分析 Apollo 配置属性注入 SpringBoot 框架,真正生效的原理

1. 客户端配置属性注入 SpringBoot 源码分析

Apollo 配置属性注入到 SpringBoot 中 Bean 的大致过程也可以分为两个步骤

  1. Apollo 组件的注入
  2. Apollo 配置属性的更新

在这里插入图片描述

1.1 Apollo 组件的注入

  1. 在 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;
    
    }
    
  2. ApolloConfigRegistrar 源码如下,这个类是注册 Apollo 组件的入口,关键方法如下:

    1. ApolloConfigRegistrar#setEnvironment() 方法会在实例创建的过程中被ParserStrategyUtils.invokeAwareMethods()回调,可以看到这个方法的逻辑非常简单,就是将 Environment 环境保存到 DefaultApolloConfigRegistrarHelper 对象中
    2. 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);
    }
    
    }
    
  3. DefaultApolloConfigRegistrarHelper#registerBeanDefinitions() 方法的处理可以分为以下几个步骤:

    1. 解析 @EnableApolloConfig 注解,将注解中配置的字符串数组处理为 namespace 数组,并调用 PropertySourcesProcessor.addNamespaces() 方法将其缓存起来
    2. 调用工具类 BeanRegistrationUtil.registerBeanDefinitionIfNotExists() 方法尝试将 Apollo 组件类包装为 BeanDefintion 并注册到容器中。可以看到注册的 Apollo 关键组件有 PropertySourcesProcessor(用于拉取 @EnableApolloConfig 配置的 namespace 的远程配置)ApolloAnnotationProcessor(用于处理 Apollo 的专用注解)SpringValueProcessor(用于处理 @Value 注解标注的类成员变量和对象方法)SpringValueDefinitionProcessor(用于处理 XML 文件中的占位符)需注意组件对象的实例化有优先级顺序,getOrder() 返回的值越小优先级越高,对象创建的时机也就越靠前

    PropertySourcesPlaceholderConfigurer是 SpringBoot 框架自身的占位符处理配置,占位符的处理主要是将 ${apollo.value} 这样的字符串解析出 关键字 apollo.value,再使用这个 key 通过 PropertySourcesPropertyResolverPropertySource 中找到对应的属性值替换掉占位符

    @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;
    }
    
  4. PropertySourcesProcessor 是 Apollo 最关键的组件之一,并且其实例化优先级也是最高的,PropertySourcesProcessor#postProcessBeanFactory() 会在该类实例化的时候被回调,该方法的处理如下:

    1. 调用 PropertySourcesProcessor#initializePropertySources() 拉取远程 namespace 配置
    2. 调用 PropertySourcesProcessor#initializeAutoUpdatePropertiesFeature() 给所有缓存在本地的 Config 配置添加监听器
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
     initializePropertySources();
     initializeAutoUpdatePropertiesFeature(beanFactory);
    }
    
  5. PropertySourcesProcessor#initializePropertySources() 方法的处理如下,可以看到和 Apollo 客户端集成 SpringBoot 的源码分析(1)- 启动时配置获取 提到的拉取配置的逻辑基本一致

    1. 调用 ConfigService#getConfig() 依次拉取各个命名空间的远端配置
    2. 调用 ConfigPropertySourceFactory#getConfigPropertySource() 缓存从远端拉取的配置,并将其包装为 PropertySource,最终将所有拉取到的远端配置聚合到一个以 ApolloPropertySources 为 key 的属性源包装类 CompositePropertySource 的内部
    3. 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);
     }
    }
    
  6. 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);
     }
    }
    
  7. ConfigPropertySource#addChangeListener() 方法如下,在 Apollo 客户端集成 SpringBoot 的源码分析(1)- 启动时配置获取 中分析过 ConfigPropertySource 包装类,我们知道这里的 this.source.addChangeListener(listener) 实际调用的是 DefaultConfig#addChangeListener() 方法

     public void addChangeListener(ConfigChangeListener listener) {
     this.source.addChangeListener(listener);
    }
    
  8. 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));
       }
     }
    }
    
  9. 回到步骤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;
    }
    
  10. ApolloProcessor#postProcessBeforeInitialization()方法的逻辑非常清晰,主要分为如下两步:

    1. 对于待处理的 bean 对象,首先调用 ApolloProcessor#findAllField() 将其定义的所有字段都通过反射找出来,然后依次调用抽象方法 processField() 处理,也就是实际的字段处理逻辑在子类中
    2. 接着调用 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;
    }
    
  11. ApolloProcessor#processField()/ApolloProcessor#processMethod() 在子类 SpringValueProcessor 相关实现如下,至此 Apollo 组件注入的分析暂告段落,其余的组件不一一分析了

    1. SpringValueProcessor#processField() 方法首先检查字段上是否有 @Value 注解,有的话调用 placeholderHelper.extractPlaceholderKeys() 方法将注解占位符字符串中的属性 key 解析出来,对于每一个 key,将其和 Field 字段等关键信息封装为 SpringValue 对象,并通过 SpringValueRegistry#register() 缓存起来
    2. 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() 监听到配置仓库变化开始分析

  1. DefaultConfig#onRepositoryChange() 方法的处理主要分为了两个步骤:

    1. 调用 DefaultConfig#updateAndCalcConfigChanges() 方法更新配置属性,并计算出实际变更的属性,此处不作详细分析
    2. 调用继承的 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);
    }
    
  2. 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();
           }
         }
       });
     }
    }
    
    
  3. 此时 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);
       }
     }
    }
    
  4. AutoUpdateConfigChangeListener#updateSpringValue() 方法的处理分为两个动作

    1. 首先调用 AutoUpdateConfigChangeListener#resolvePropertyValue() 方法借助 SpringBoot 的组件将 @Value 中配置的占位符替换为 PropertySource 中的对应 key 的属性值,此处涉及到 Spring 创建 Bean 对象时的属性注入机制,比较复杂,暂不作深入分析
    2. 调用 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);
     }
    }
    
  5. 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);
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值