配置SpringBoot-應用配置如何被加載

源碼之下,沒有祕密.html


接上篇, 探索如何解決保證自定義配置能被加載,而且兼容 springboot 默認全部配置.
回答上篇留下的幾個問題java

  • 有沒有更優雅的解決此問題的方法?
  • 爲什麼 Spring Boot 的配置文件加載順序是這般定義?
  • Spring Boot 加載配置是如何實現的?
  • Spring Cloud Config 等配置中心的配置是如何結合到具體應用中的?
  • Spring Cloud Config 等配置中心的配置支持日誌系統配置文件位置的配置嗎?

先看源碼

核心代碼就一行

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
複製代碼

Spring Boot 2.0.4 版, org.springframework.boot.SpringApplication line: 320git

爲何核心就此一行 在此行代碼執行以前, environment 不出意外是沒有初始化的(後文解釋). 在此行代碼執行以後, environment 正式建立完畢. 在此以後繼續加載配置文件, 有些配置屬性是不生效的, 好比 spring.profiles.active.github

深刻看看

private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();  // 1
    configureEnvironment(environment, applicationArguments.getSourceArgs()); // 2 
    listeners.environmentPrepared(environment); // 3
    bindToSpringApplication(environment);
    if (this.webApplicationType == WebApplicationType.NONE) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}
複製代碼

核心關注點就方法前兩行.web

getOrCreateEnvironment

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    if (this.webApplicationType == WebApplicationType.SERVLET) {
        return new StandardServletEnvironment();
    }
    return new StandardEnvironment();
}
複製代碼

這個方法有點意思, 若是 SpringApplication 實例的 屬性 environment 是有值的話, 返回的就是當前實例的 environment. 這是個切入點, 若是能保證這個 environment 是已經加載了自定義配置文件的 environment, 天然能夠保證 Spring Boot 的全部配置均支持.spring

configureEnvironment

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    configurePropertySources(environment, args);
    configureProfiles(environment, args);
}
複製代碼

這是另外一個切入點, 關注一下這個方法是被 protectted 修飾的, 能夠重寫之. 方法體裏的兩個方法也是 protected 修飾, configurePropertySources(environment, args) 這個方法實現了上篇提到的 官方文檔加載順序的 第 4-15 步驟, 細節不談. 能夠複寫以支持自定義的配置文件加載.springboot

再看看第三行

listeners.environmentPrepared(environment)
複製代碼

衆所周知, Spring 擴展點尤爲多, Spring Boot 也不例外.
這個 listeners 是什麼東西?app

class SpringApplicationRunListeners {
	private final List<SpringApplicationRunListener> listeners;
}
複製代碼

SpringApplicationRunListener 又是什麼?ide

public interface SpringApplicationRunListener {

	/** * Called immediately when the run method has first started. Can be used for very * early initialization. */
	void starting();

	/** * Called once the environment has been prepared, but before the * {@link ApplicationContext} has been created. * @param environment the environment */
	void environmentPrepared(ConfigurableEnvironment environment);

	/** * Called once the {@link ApplicationContext} has been created and prepared, but * before sources have been loaded. * @param context the application context */
	void contextPrepared(ConfigurableApplicationContext context);

	/** * Called once the application context has been loaded but before it has been * refreshed. * @param context the application context */
	void contextLoaded(ConfigurableApplicationContext context);

	/** * The context has been refreshed and the application has started but * {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner * ApplicationRunners} have not been called. * @param context the application context. * @since 2.0.0 */
	void started(ConfigurableApplicationContext context);

	/** * Called immediately before the run method finishes, when the application context has * been refreshed and all {@link CommandLineRunner CommandLineRunners} and * {@link ApplicationRunner ApplicationRunners} have been called. * @param context the application context. * @since 2.0.0 */
	void running(ConfigurableApplicationContext context);

	/** * Called when a failure occurs when running the application. * @param context the application context or {@code null} if a failure occurred before * the context was created * @param exception the failure * @since 2.0.0 */
	void failed(ConfigurableApplicationContext context, Throwable exception);
}
複製代碼

比較明顯了, Spring Boot 啓動生命週期的監聽器. 若是能在 void starting();中初始化 environment 並設置到 SpringApplication environment 屬性, 問題解決.spring-boot

肯定方案

  1. 實現 SpringApplicationRunListener , 在 staring 方法中加載自定義配置到 environment 中.
  2. 複寫 configureEnvironment 方法.

相對來說, 方案 2 成本略高. 採用方案1

落地

方案肯定後, 實現很簡單. 核心代碼

@Override
  public void starting() {

    ConfigurableEnvironment environment = this.getOrCreateEnvironment();

    Class<?> applicationClass = application.getMainApplicationClass();
    DtConfig annotation = applicationClass.getAnnotation(DtConfig.class);
    if (annotation == null) {
      return;
    }

    String path = annotation.value();

    Resource resource = null;
    if (path.startsWith("classpath")) {
      resource = new ClassPathResource(path.substring(10));
    } else {
      try {
        resource = new FileUrlResource(path);
      } catch (MalformedURLException e) {
        resource = new FileSystemResource(path);
      }
    }

    PropertySource<?> propertySource = loadProperties("Dtconfig", resource, loader);

    environment.getPropertySources().addFirst(propertySource);

    application.setEnvironment(environment);
  }
複製代碼

詳細代碼, 傳送門 sb-config

問題解答

  • 有沒有更優雅的解決此問題的方法?
    有, 如上.
  • 爲什麼 Spring Boot 的配置文件加載順序是這般定義?
    暫且跳過, 沒什麼好說的, 約定優於配置, 配置分層級, 越靠近用戶端, 配置的優先級越高.
  • Spring Boot 加載配置是如何實現的?
    參閱 configurePropertySources 方法, 過於細節.
  • Spring Cloud Config 等配置中心的配置是如何結合到具體應用中的?
    • Spring Cloud Config, Spring Boot 的自動配置
    • apollo, Spring Boot 的自動配置
  • Spring Cloud Config 等配置中心的配置支持日誌系統配置文件位置的配置嗎?
    不支持, 幾乎能夠斷言, 若是配置中心沒有采用上文的實現方式, 這個問題是解決不了的.

REF

相關文章
相關標籤/搜索