首頁 > 軟體

spring boot微服務場景下apollo載入過程解析

2022-02-21 13:05:36

整合使用

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載入過程的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com