spring boot微服务场景下apollo加载过程解析

 更新时间:2022年02月21日 11:34:50   作者:kl  
apollo 是一个开源的配置中心项目,功能很强大,apollo 本身的配置项并不复杂,但是因为配置的路径特别多,非常容易搞混了, 所以本文试图聚焦 spring-boot 的场景,在 spring-boot 微服务场景下,搞清楚 apollo-client的加载过程

集成使用

1、添加 gradle 依赖

implementation "com.ctrip.framework.apollo:apollo-client:1.6.0"

2、配置 application.properties

apollo 自身的配置共包含 9 项,必要配置只有 3 项,其他的都是可选的配置。apollo 在 spring-boot 环境下的配置命名和 System 参数的命名保持了一直,最终 spring 的配置会注入到 System 中,具体的逻辑下文分析。

必须配置

#应用的ID
app.id = java-project
# apollo 的 config-service 服务发现地址
apollo.meta = http://apollo.meta
# 启用 apollo
apollo.bootstrap.enabled = true

可选配置

# 在日志系统初始化前加载 apollo 配置
apollo.bootstrap.eagerLoad.enabled=true
# 加载的命名空间,默认加载 application ,多个以逗号隔开
apollo.bootstrap.namespaces = application
# apollo 的安全拉取 secret 配置
apollo.accesskey.secret = xx
# 集群配置
apollo.cluster = hk
# 缓存路径
apollo.cacheDir = /opt
# 是否保持和 apollo 配置页面的配置顺序一致
apollo.property.order.enable = true

加载过程解析

public class ApolloApplicationContextInitializer implements ApplicationContextInitializer, EnvironmentPostProcessor, Ordered {
  public static final int DEFAULT_ORDER = 0;
  private static final Logger logger = LoggerFactory.getLogger(ApolloApplicationContextInitializer.class);
  private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
  private static final String[] APOLLO_SYSTEM_PROPERTIES = {"app.id", ConfigConsts.APOLLO_CLUSTER_KEY,
      "apollo.cacheDir", "apollo.accesskey.secret", ConfigConsts.APOLLO_META_KEY, PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE};
  private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector.getInstance(ConfigPropertySourceFactory.class);
  private int order = DEFAULT_ORDER;
  @Override
  public void initialize(ConfigurableApplicationContext context) {
    ConfigurableEnvironment environment = context.getEnvironment();
    if (!environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false)) {
      logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${{}}", context, PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
      return;
    }
    logger.debug("Apollo bootstrap config is enabled for context {}", context);
    initialize(environment);
  }
  /**
   * Initialize Apollo Configurations Just after environment is ready.
   *
   * @param environment
   */
  protected void initialize(ConfigurableEnvironment environment) {
    if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
      //already initialized
      return;
    }
    String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
    logger.debug("Apollo bootstrap namespaces: {}", namespaces);
    ListnamespaceList = 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.getPropertySources().addFirst(composite);
  }
  /**
   * To fill system properties from environment config
   */
  void initializeSystemProperty(ConfigurableEnvironment environment) {
    for (String propertyName : APOLLO_SYSTEM_PROPERTIES) {
      fillSystemPropertyFromEnvironment(environment, propertyName);
    }
  }
  private void fillSystemPropertyFromEnvironment(ConfigurableEnvironment environment, String propertyName) {
    if (System.getProperty(propertyName) != null) {
      return;
    }
    String propertyValue = environment.getProperty(propertyName);
    if (Strings.isNullOrEmpty(propertyValue)) {
      return;
    }
    System.setProperty(propertyName, propertyValue);
  }
  /**
   *
   * In order to load Apollo configurations as early as even before Spring loading logging system phase,
   * this EnvironmentPostProcessor can be called Just After ConfigFileApplicationListener has succeeded.
   *
   * 
   * The processing sequence would be like this: 
   * Load Bootstrap properties and application properties -----> load Apollo configuration properties ----> Initialize Logging systems
   *
   * @param configurableEnvironment
   * @param springApplication
   */
  @Override
  public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) {
    // should always initialize system properties like app.id in the first place
    initializeSystemProperty(configurableEnvironment);
    Boolean eagerLoadEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED, Boolean.class, false);
    //EnvironmentPostProcessor should not be triggered if you don't want Apollo Loading before Logging System Initialization
    if (!eagerLoadEnabled) {
      return;
    }
    Boolean bootstrapEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false);
    if (bootstrapEnabled) {
      initialize(configurableEnvironment);
    }
  }
  /**
   * @since 1.3.0
   */
  @Override
  public int getOrder() {
    return order;
  }
  /**
   * @since 1.3.0
   */
  public void setOrder(int order) {
    this.order = order;
  }
}

apollo 在 spring-boot 中的加载逻辑都在如上的代码中了,代码的关键是实现了两个 spring 生命周期的接口,

  • ApplicationContextInitializer

在被 ConfigurableApplicationContext.refresh()刷新之前初始化 ConfigurableApplicationContext 的回调接口。

  • EnvironmentPostProcessor

比 ApplicationContextInitializer 的加载时机还要提前,此时 spring-boot 的日志系统还未初始化,

postProcessEnvironment 方法逻辑解析

1、初始化 System 的配置,将 spring 上下文中的配置(环境变量、System 参数、application.properties) 拷贝到 System 配置中, 如果 System 已经存在同名的配置则跳过,保证了 -D 设置的 System 参数的最高优先级。但是也带来了一个隐含的问题,默认,apollo 的配置设计支持从环境变量中取值,也遵循了环境变量大写的规范,将 System 参数的 "." 换成 "_" 拼接,然后变成大写。 比如 apollo.meta 对应环境变量的 APOLLO_META。但是在 spring-boot 的环境下,因为 spring 的配置系统默认也会加载环境变量的配置,最终在环境变量里配置 apollo.meta 也会生效。甚至比正确配置的 APOLLO_META 环境变量值的优先级还高。

2、根据 apollo.bootstrap.eagerLoad.enabled 和 apollo.bootstrap.enabled 的配置来判断是否在这个阶段初始化 apollo。 postProcessEnvironment() 执行的时候, 此时日志系统并未初始化,在这个阶段加载 apollo,可以解决将日志配置托管到 apollo 里直接生效的问题。 带来的问题是, 假如在这个阶段的 apollo 加载出现问题,由于日志系统未初始化,看不到 apollo 的加载日志,不方便定位 apollo 的加载问题。 所以博主建议,如果有托管日志配置的场景,可以先不启用 apollo.bootstrap.eagerLoad.enabled 的配置,等 apollo 集成完成后在启用。
 

initialize 方法逻辑解析

1、根据 apollo.bootstrap.enabled 的配置来判断,是否在这个阶段初始化 apollo ,如果此时 spring 上下文中已经包含了 apollo 的 PropertySources,代表 apollo 已经 初始化过,则直接 return 掉

2、根据 apollo.bootstrap.namespaces 的配置,默认不配置为 "application" ,依次获取对应的 namespace 的配置, 并将配置使用 addFirst() 具有最高优先级属性源的设置方法, 添加到了 spring 的配置上下文中。这里解释了为什么 apollo 的配置的优先级最高,比 application.properties 中直接配置都要高, 这个优先级的问题会经常闹乌龙,在本地开发调试阶段,会直接在 application.properties 里调试配置,然后怎么改都不生效,因为 apollo 里 存在了同名的配置,启动的时候直接覆盖了本地的配置。博主也犯过几次这个错误

结语

上面列出的 9 项 apollo 配置,只有三项配置(apollo.bootstrap.enabled、apollo.bootstrap.eagerLoad.enabled、apollo.bootstrap.namespaces)是在 spring-boot 启动过程中用到的,其他的配置都被透传到 System ,供 apollo 底层 sdk 使用。 基于此而发现了一个 apollo 初始化配置时的小彩蛋,在 spring-boot 应用里,如果使用环境变量来驱动 apollo 的配置项,则带 "." 风格的配置(apollo.meta)和 "_" 风格的大写配置(APOLLO_META)的效果是等价的,并且如果两个配置同时存在环境变量中,前者的优先级要高于后者

以上就是spring boot微服务场景下apollo加载过程解析的详细内容,更多关于spring-boot下apollo加载过程的资料请关注脚本之家其它相关文章!

相关文章

  • 如何设置Spring Boot测试时的日志级别

    如何设置Spring Boot测试时的日志级别

    Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。下面我们来一起学习一下吧
    2019-06-06
  • 详解Java中Hibernate的基本原理

    详解Java中Hibernate的基本原理

    持久化是将程序数据在持久状态和瞬时状态间转换的机制。JDBC就是一种持久化机制。文件IO也是一种持久化机制。下面通过本文给大家介绍Java中Hibernate的基本原理,需要的朋友参考下吧
    2017-09-09
  • 详解Java 微服务架构

    详解Java 微服务架构

    这篇文章主要介绍了Java 微服务架构的相关资料,帮助大家更好的理解和使用Java,感兴趣的朋友可以了解下
    2021-02-02
  • Java手动方式创建枚举类示例

    Java手动方式创建枚举类示例

    这篇文章主要介绍了Java手动方式创建枚举类,结合实例形式分析了java创建枚举类的方法及相关操作技巧,需要的朋友可以参考下
    2019-08-08
  • Intellij IDEA神器居然还有这些小技巧

    Intellij IDEA神器居然还有这些小技巧

    Intellij IDEA真是越用越觉得它强大,它总是在我们写代码的时候,不时给我们来个小惊喜,本文给大家主要介绍一些你可能不知道的但是又实用的小技巧,感兴趣的朋友跟随小编一起看看吧
    2021-01-01
  • JAVA中使用MD5加密实现密码加密

    JAVA中使用MD5加密实现密码加密

    本篇文章主要介绍JAVA中使用MD5加密实现密码加密,很多地方都要存储用户密码,这里整理了详细的代码,有需要的小伙伴可以参考下
    2017-07-07
  • JAVA生成短8位UUID的实例讲解

    JAVA生成短8位UUID的实例讲解

    这篇文章主要介绍了JAVA生成短8位UUID的实例讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • 基于FeignClient调用超时的处理方案

    基于FeignClient调用超时的处理方案

    这篇文章主要介绍了基于FeignClient调用超时的处理方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Java编程使用Runtime和Process类运行外部程序的方法

    Java编程使用Runtime和Process类运行外部程序的方法

    这篇文章主要介绍了Java编程使用Runtime和Process类运行外部程序的方法,结合实例形式分析了java使用Runtime.getRuntime().exec()方法运行外部程序的常见情况与操作技巧,需要的朋友可以参考下
    2017-08-08
  • 浅谈Java常见的排序算法

    浅谈Java常见的排序算法

    今天给大家带来的是关于Java的相关知识,文章围绕着Java常见的排序算法展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06

最新评论