likes
comments
collection
share

企业级代码探究: @Value + Apollo动态刷新原理~

作者站长头像
站长
· 阅读数 14

前言

在企业项目开发过程中,我们往往会为了代码的灵活性、可扩展性从而考虑为代码逻辑接入配置中心

例如:

  1. 线程池接入配置中心,实现动态线程池,可灵活修改线程数.
  2. 为了测试同学测试、产品线上回归等,利用配置中心配置uid等信息,实现白名单效果。
  3. 可变化性需求: 运营位、banner图的配置等,通常都是基于配置中心的动态化配置。

等等等等。

总而言之,配置中心在我们的日常开发过程中,已经是必不可少的了~

今天带大家了解的是SpringBoot @Value注解结合Apollo配置中心可动态化刷新的原理~


小案例

先来个小案例带大家稍微熟悉一下

@Configuration
@Data
public class Config {
    
    @Value("${test.value}")
    public String testValue;
  
}
@SpringBootTest
public class ValueApolloTest {

   @Resource
   private Config config;

   @Test
   public void test() {
      System.out.println(config.getTestValue());
   }

}

先在Apollo配置下test.value的值为测试value注解-v1

企业级代码探究: @Value + Apollo动态刷新原理~

控制台成功打印了测试value注解-v1

企业级代码探究: @Value + Apollo动态刷新原理~

再改一下test.value的值

企业级代码探究: @Value + Apollo动态刷新原理~

控制台也成功打印了改变后的值

企业级代码探究: @Value + Apollo动态刷新原理~


原理

上一小节简单介绍了下@Value注解结合Apollo的使用,这节就正式开始说明原理了

建议先看总结,再来看源码,简单易懂~


Apollo结合SpringBoot自动装配

apollo-client创建了spring.factories文件,其中定义了ApolloAutoConfiguration路径,配合SpringBoot进行自动装配

企业级代码探究: @Value + Apollo动态刷新原理~

ApolloAutoConfiguration中定义了一个bean: ConfigPropertySourcesProcessor

@Configuration
@ConditionalOnProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED)
@ConditionalOnMissingBean(PropertySourcesProcessor.class)
public class ApolloAutoConfiguration {

  @Bean
  public ConfigPropertySourcesProcessor configPropertySourcesProcessor() {
    return new ConfigPropertySourcesProcessor();
  }
}

而在ConfigPropertySourcesProcessor中,其实现了BeanDefinitionRegistryPostProcessor,并在postProcessBeanDefinitionRegistry中调用了ConfigPropertySourcesProcessorHelper#postProcessBeanDefinitionRegistry

public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor
    implements BeanDefinitionRegistryPostProcessor {

  private ConfigPropertySourcesProcessorHelper helper = ServiceBootstrap.loadPrimary(ConfigPropertySourcesProcessorHelper.class);

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    helper.postProcessBeanDefinitionRegistry(registry);
  }
}

其目的就是为了注册自定义的bean,即下面代码所示:

public class DefaultConfigPropertySourcesProcessorHelper implements ConfigPropertySourcesProcessorHelper {

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
    propertySourcesPlaceholderPropertyValues.put("order", 0);

    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
        PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry,
        AutoUpdateConfigChangeListener.class.getName(), AutoUpdateConfigChangeListener.class);
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
        ApolloAnnotationProcessor.class);
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(),
        SpringValueProcessor.class);

    processSpringValueDefinition(registry);
  }
}

一共注册了4个bean

  • PropertySourcesPlaceholderConfigurer
  • AutoUpdateConfigChangeListener
  • ApolloAnnotationProcessor
  • SpringValueProcessor

我们本文讲的是@Value注解与Apollo的自定刷新机制,所以与之相关的是AutoUpdateConfigChangeListenerSpringValueProcessor


SpringValueProcessor 处理@Value

SpringValueProcessor继承了ApolloProcessor,而ApolloProcessor又实现了BeanPostProcessor,那么在bean初始化前后会分别调用postProcessBeforeInitialization、postProcessAfterInitialization

postProcessBeforeInitialization中,会通过反射获取当前class的所有field和method,之后调用模版方法进行处理~

public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor, BeanFactoryAware {
}
public abstract class ApolloProcessor implements BeanPostProcessor, PriorityOrdered {

  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName)
      throws BeansException {
    Class clazz = bean.getClass();
    // 反射获取class所有字段并处理
    for (Field field : findAllField(clazz)) {
      processField(bean, beanName, field);
    }
    // 反射获取class所有method并处理
    for (Method method : findAllMethod(clazz)) {
      processMethod(bean, beanName, method);
    }
    return bean;
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    return bean;
  }

  protected abstract void processField(Object bean, String beanName, Field field);

  protected abstract void processMethod(Object bean, String beanName, Method method);
  
}

SpringValueProcessor重写了processField和processMethod,我们以processField为例

public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor, BeanFactoryAware {
  
  private final SpringValueRegistry springValueRegistry;

  @Override
  protected void processField(Object bean, String beanName, Field field) {
    // 字段上是否有Value注解
    Value value = field.getAnnotation(Value.class);
    if (value == null) {
      return;
    }

    // 进行注册
    doRegister(bean, beanName, field, value);
  }

  private void doRegister(Object bean, String beanName, Member member, Value value) {
    // 提取占位符中的key
    Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
    if (keys.isEmpty()) {
      return;
    }

    for (String key : keys) {
      SpringValue springValue;
      if (member instanceof Field) {
        Field field = (Field) member;
        springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
      } else if (member instanceof Method) {
        Method method = (Method) member;
        springValue = new SpringValue(key, value.value(), bean, beanName, method, false);
      } else {
        logger.error("Apollo @Value annotation currently only support to be used on methods and fields, "
                     + "but is used on {}", member.getClass());
        return;
      }
      
      // 注册到SpringValueRegistry
      springValueRegistry.register(beanFactory, key, springValue);
      logger.info("Monitoring {}", springValue);
    }
  }

}

processField会判断字段上是否存在@Value注解,如果存在则会将相关信息(bean、beanName、key、field)等封装为SpringValue并注册到SpringValueRegistry


SpringValueRegistry 维护@Value相关的field、method

SpringValueRegistry中维护了registry这个mapkeyBeanFactoryvaluekey -> Collection<SpringValue>map

public class SpringValueRegistry {
  private static final Logger logger = LoggerFactory.getLogger(SpringValueRegistry.class);

  private static final long CLEAN_INTERVAL_IN_SECONDS = 5;
  private final Map<BeanFactory, Multimap<String, SpringValue>> registry = Maps.newConcurrentMap();
  private final AtomicBoolean initialized = new AtomicBoolean(false);
  private final Object LOCK = new Object();

  public void register(BeanFactory beanFactory, String key, SpringValue springValue) {
    if (!registry.containsKey(beanFactory)) {
      synchronized (LOCK) {
        if (!registry.containsKey(beanFactory)) {
          // value初始化
          registry.put(beanFactory, Multimaps.synchronizedListMultimap(LinkedListMultimap.<String, SpringValue>create()));
        }
      }
    }

    // 注册
    registry.get(beanFactory).put(key, springValue);

    // 懒 初始化
    if (initialized.compareAndSet(false, true)) {
      initialize();
    }
  }
}

AutoUpdateConfigChangeListener 自动刷新!

public class AutoUpdateConfigChangeListener implements ConfigChangeListener,
ApplicationListener<ApolloConfigChangeEvent>, ApplicationContextAware {

  private final SpringValueRegistry springValueRegistry;
  private ConfigurableBeanFactory beanFactory;

  public AutoUpdateConfigChangeListener() {
    // ......
    // 实例化SpringValueRegistry
    this.springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class);
    // ......
  }

  @Override
  public void onChange(ConfigChangeEvent changeEvent) {
    // 获取变动的keys
    Set<String> keys = changeEvent.changedKeys();
    if (CollectionUtils.isEmpty(keys)) {
      return;
    }
    
    // 遍历变动的keys
    for (String key : keys) {
      // 看看SpringValueRegistry是否存在该key
      Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);
      if (targetValues == null || targetValues.isEmpty()) {
        continue;
      }

      // 刷新value
      for (SpringValue val : targetValues) {
        updateSpringValue(val);
      }
    }
  }

  @Override
  public void onApplicationEvent(ApolloConfigChangeEvent event) {
    if (!configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
      return;
    }
    // 处理change事件
    this.onChange(event.getConfigChangeEvent());
  }

  private void updateSpringValue(SpringValue springValue) {
    try {
      // 解析springValue,拿到新的value值
      Object value = resolvePropertyValue(springValue);
      
      // 更新value值
      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);
    }
  }
}

AutoUpdateConfigChangeListener实现了SpringApplicationListener接口,监听ApolloConfigChangeEvent事件

Apollo配置发生变动时,会发布ApolloConfigChangeEvent事件,从而会回调onApplicationEvent方法

onApplicationEvent中,会调用onChange,检查此次变动的key是否存在于SpringValueRegistry

如果存在,则会解析出新的value值,调用SpringValue#update进行更新

public class SpringValue {
  
  public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
    if (isField()) {
      // 更新字段
      injectField(newVal);
    } else {
      // 通过方法更新
      injectMethod(newVal);
    }
  }
}

private void injectField(Object newVal) throws IllegalAccessException {
  // 拿到bean对象
  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);
}

本质上就是通过反射更新~


总结

整体源码看起来并不是很复杂,下面再说下整体的刷新流程

Apollo利用SpringBoot自动装配机制,自定义注册了几个bean

  • SpringValueProcessor实现了BeanPostProcessorbean初始化之前,会检查该bean是否存在被@Value注解修饰的field or method,如果存在,则注册到SpringValueRegistry
  • AutoUpdateConfigChangeListener监听ApolloConfigChangeEvent事件,并维护了SpringValueRegistry, 当Apollo发生配置更新时,检查变动的key是否存在于SpringValueRegistry,如果存在,则解析新的value并通过反射进行更新,从而实现动态刷新。

后话

我是 皮皮虾 ,会在以后的日子里跟大家一起学习,一起进步!

觉得文章不错的话,可以在 掘金 关注我~