../how-sb-application-configuration-loading

配置 SpringBoot, 应用配置如何被加载

Published:

spring springboot config

源码之下,没有秘密.


接上篇, 探索如何解决保证自定义配置能被加载,并且兼容 springboot 默认所有配置.

回答上篇留下的几个问题

先看源码

核心代码就一行

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

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

为什么核心就此一行

在此行代码执行之前, environment 不出意外是没有初始化的(后文解释).

在此行代码执行之后, environment 正式创建完毕. 在此之后继续加载配置文件, 有些配置属性是不生效的, 比如 spring.profiles.active.

深入看看

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

核心关注点就方法前两行.

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 的所有配置均支持.

configureEnvironment

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

这是另一个切入点, 关注一下这个方法是被 protectted 修饰的, 可以重写之.

方法体里的两个方法也是 protected 修饰, configurePropertySources(environment, args) 这个方法实现了上篇提到的 官方文档加载顺序的 第 4-15 步骤, 细节不谈. 可以复写以支持自定义的配置文件加载.

再看看第三行

listeners.environmentPrepared(environment)

众所周知, Spring 扩展点尤其多, Spring Boot 也不例外. 这个 listeners 是什么东西?

class SpringApplicationRunListeners {
	private final List<SpringApplicationRunListener> listeners;
}

SpringApplicationRunListener 又是什么?

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 属性, 问题解决.

确定方案

  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

问题解答

REF