../how-sb-application-configuration-loading
配置 SpringBoot, 应用配置如何被加载
Published:
源码之下,没有秘密.
接上篇, 探索如何解决保证自定义配置能被加载,并且兼容 springboot 默认所有配置.
回答上篇留下的几个问题
- 有没有更优雅的解决此问题的方法?
- 为何 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: 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 属性, 问题解决.
确定方案
- 实现 SpringApplicationRunListener , 在 staring 方法中加载自定义配置到 environment 中.
- 复写 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 等配置中心的配置支持日志系统配置文件位置的配置吗? 不支持, 几乎可以断言, 如果配置中心没有采用上文的实现方式, 这个问题是解决不了的.