源碼之下,沒有祕密.html
接上篇, 探索如何解決保證自定義配置能被加載,而且兼容 springboot 默認全部配置.
回答上篇留下的幾個問題java
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
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
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
相對來說, 方案 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