Springboot默認加載application.yml原理以及擴展
SpringApplication.run(...)默認會加載classpath下的application.yml或application.properties配置文件。公司要求搭建的框架默認加載一套默認的配置文件demo.properties,讓開發人員實現“零”配置開發,但是前提如果開發人員在application.yml或application.properties文件中自定義配置,則會“覆蓋”默認的demo.properties文件,按照Springboot外部化配置的特性(優先使用先加載的),只要demo.properties配置在application.yml或application.properties 配置之后加載到environment中即可。
一、SpirngApplication.run(...)源碼分析
通過源碼分析,得知Springboot加載配置文件,是利用Spring的事件機制,通過EventPublishingRunListener取發布准備資源事件ApplicationEnvironmentPreparedEvent,被ConfigFileApplicationListener監聽到,從而來實現資源的加載
具體源碼如下:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//這里是擴展的關鍵點
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//這里是加載資源的關鍵
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
....
}
//從方法名稱來看就是准備environment的即配置信息
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
//這里默認EventPublishingRunListener發布ApplicationEnvironmentPreparedEvent事件
//讓監聽器ConfigFileApplicationListener加載配置文件
//這個listeners就是我們擴展的地方
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
SpirngApplication.run(...)方法中有個重要的擴展點方法getRunListeners(args);
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
//可擴展的關鍵點SpringFactoriesLoader
//SpringFactoriesLoader會去加載META-INF/spring.factories文件,並根據
//type過濾出符合要求的類
//比如這里的type對應的是:SpringApplicationRunListener
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
Springboot默認提供的META-INF/spring.factories,這里就是我們可以擴展的地方
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
至此資源加載的大概流程就分析完了,下面是我們的擴展
二、擴展——自定義加載配置文件(demo.properties)
通過上述源碼分析得知:只需要在項目中添加META-INF/spring.factories,並配置SpringApplicationRunListener為我們自定義的來即可
1、在項目中的resources下創建META-INF/spring.factories
org.springframework.boot.SpringApplicationRunListener=\
com.demo.module.ApplicatonEnvironDemoListener
2、ApplicatonEnvironDemoListener的代碼
package com.chyjr.hyboot.demo.module;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import java.io.IOException;
import java.util.Properties;
public class ApplicatonEnvironDemoListener implements
SpringApplicationRunListener,PriorityOrdered {
private SpringApplication application;
private String[] args;
/**
* 通過反射創建該實例對象的,構造方法中的參數要加上如下參數
*/
public ApplicatonEnvironDemoListener(SpringApplication application,String[] args){
this.application = application;
this.args = args;
}
/**
* 在准備環境之間調用
* SpringApplication#run -> listeners.starting();
*/
@Override
public void starting() {
System.out.println("starting-----");
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
Properties properties = new Properties();
try {
//demo.properties就是我們自定義的配置文件,extension是自定義目錄
properties.load(this.getClass().getClassLoader().
getResourceAsStream("extension/demo.properties"));
PropertySource propertySource =new
PropertiesPropertySource("demo",properties);
//PropertySource是資源加載的核心
MutablePropertySources propertySources = environment.getPropertySources();
//這里添加最后
propertySources.addLast(propertySource);
} catch (IOException e) {
e.printStackTrace();
}
}
//省略其他方法
...
/**
* 這里可以設置該配置文件加載的順序,在application.yml之前還是之后
* EventPublishingRunListener#getOrder方法返回 “0”,按照需求這里我們這是比0大,
* 即在application.yml之后加載,這樣在application.yml配置時,可以“覆蓋”my.yml
* 這里用“覆蓋”可能不合適,意思到了就好
*/
@Override
public int getOrder() {
return 1;
}
}