让本地配置优先于 apollo 配置

178 阅读2分钟

为什么需要让本地配置优先于 apollo 呢?当你在本地调试时,开发环境的配置可能无法满足调试条件,例如要升级 mysql,需要在本地调试访问高版本的 mysql,但开发环境的 mysql 版本较低,或者要本地调试访问一个新版的 rpc 接口,或者是要调试访问一个 mock 接口,为了能满足调试条件而不改动开发环境配置影响其他童鞋使用,需要本地配置优先于 apollo 配置。

apollo 配置加载

apollo 主要有两类配置源,分别是 ApolloBootstrapPropertySources 和 ApolloPropertySources,ApolloBootstrapPropertySources 主要是提供 bean 工厂初始化时所需的配置参数,例如在 @condition 中使用 apollo 中的配置,ApolloPropertySources 主要供 bean 容器内的 bean 实例使用。

接下先看看 ApolloBootstrapPropertySources 的加载时机

// com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer
@Override
public void initialize(ConfigurableApplicationContext context) {
  ConfigurableEnvironment environment = context.getEnvironment();
  // apollo.bootstrap.enabled = true 才支持
  String enabled = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "false");
  if (!Boolean.valueOf(enabled)) {
    return;
  }

  // 获取需要加载的namespaces
  String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
  List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);

  CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
  for (String namespace : namespaceList) {
    Config config = ConfigService.getConfig(namespace);
    composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
  }
  // 添加到environment动态配置源的首位
  environment.getPropertySources().addFirst(composite);
}

ApolloApplicationContextInitializer 实现了 ApplicationContextInitializer,在 SpringApplication#prepareContext 中执行。根据 apollo.bootstrap.enabled 控制是否开启加载,及根据 apollo.bootstrap.namespaces 决定要加载哪些 namespaces。

接下来看看

// com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  if (INITIALIZED.compareAndSet(false, true)) {
    // 初始化配置源
    initializePropertySources();
    // 初始化热更新相关功能
    initializeAutoUpdatePropertiesFeature(beanFactory);
  }
}

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));
    }
  }

  // add after the bootstrap property source or to the first
  if (environment.getPropertySources()
      .contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
    environment.getPropertySources()
        .addAfter(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, composite);
  } else {
    environment.getPropertySources().addFirst(composite);
  }
}

PropertySourcesProcessor 继承自 BeanFactoryPostProcessor,会在 bean 工厂初始化完成后,bean 扫描前完成配置源的加载,并且配置源会在 ApolloBootstrapPropertySources 之后。

添加本地配置源

知道了 apollo 配置源的加载时机和在环境对象的可变配置源集合的位置之后,就能很容易想到,只要在 bean 初始化前在 ApolloBootstrapPropertySources 前添加本地的配置源即可实现本地配置覆盖 apollo 配置。

@Component
@ConditionalOnProperty(
    name = {"localCoverApollo"},
    havingValue = "true"
)
public class ApolloLocalPropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware {
    private static final Logger log = LoggerFactory.getLogger(ApolloLocalPropertySourcesProcessor.class);
    private ConfigurableEnvironment environment;

    public ApolloLocalPropertySourcesProcessor() {
    }

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            Properties properties = PropertiesLoaderUtils.loadProperties(new ClassPathResource("application.properties"));
            PropertiesPropertySource propertySource = new PropertiesPropertySource("local", properties);
            this.environment.getPropertySources().addFirst(propertySource);
            log.info("本地配置覆盖apollo配置成功,本地配置:{}", properties);
        } catch (Exception var4) {
            Exception e = var4;
            log.warn("本地配置覆盖apollo配置失败", e);
        }

    }

    public void setEnvironment(Environment environment) {
        this.environment = (ConfigurableEnvironment)environment;
    }
}

以上是具体实现,总体来说还是非常简单的。如果对上面的代码还有疑问,可以研究一下 Enviroment 获取配置的实现。