springboot加载配置文件
springboot启动时候是通过ConfigFileApplicationListener类加载配置文件,这个类继承了EnvironmentPostProcesser和SmartApplicationListener,在springboot启动的时候会调用这个监听器。
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
如果事件是加载配置文件ApplicationEnvironmentPreparedEvent类型,会调用
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
ConfigFileApplicationListener也继承了EnvironmentPostProcesser,所以会在postProcessors数组中加入,循环所有的EnvironmentPostProcessor类型调用postProcessorEnvironmet方法加载配置文件。
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
在addPropertySources方法中调用Loader类的load方法将配置文件加载到environment中,这个类是ConfigFileApplicationListener中的私有类。
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
私有的Loader类有如下字段
private class Loader {
private final Log logger = ConfigFileApplicationListener.this.logger;
private final ConfigurableEnvironment environment;
private final ResourceLoader resourceLoader;
//有两种分别是PropertiesPropertySourceLoader和
YmalPropertySourceLoader
private final List<PropertySourceLoader> propertySourceLoaders;
//加载配置文件profile
private Deque<Profile> profiles;
//已经处理过的profile
private List<Profile> processedProfiles;
private boolean activatedProfiles;
//不同profile的PropertySource
private Map<Profile, MutablePropertySources> loaded;
//缓存已经加载的文件
private Map<DocumentsCacheKey, List<Document>> loadDocumentsCache = new HashMap<>();
load方法如下
public void load() {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
//初始化需要加载的profile
initializeProfiles();
//循环处理profiles队列加载配置文件
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (profile != null && !profile.isDefaultProfile()) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter,
addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
}
initializeProfiles()方法如下
private void initializeProfiles() {
//先加入null的profile
this.profiles.add(null);
Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
//加入要激活的profile
addActiveProfiles(activatedViaProperty);
//加入default的profile
if (this.profiles.size() == 1) { // only has null profile
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
this.profiles.add(defaultProfile);
}
}
}
然后继续调用load方法,load方法中有两个参数,this::getPositiveProfileFilter和addToLoaded(MutablePropertySources::addLast, false)
private DocumentFilter getPositiveProfileFilter(Profile profile) {
return (Document document) -> {
if (profile == null) {
return ObjectUtils.isEmpty(document.getProfiles());
}
return ObjectUtils.containsElement(document.getProfiles(),
profile.getName())
&& this.environment.acceptsProfiles(document.getProfiles());
};
}
这个方法根据profile过滤不同的document配置文件,返回是一个DocumentFilter的函数是接口,这个接口也是定义在这个类中
@FunctionalInterface
private interface DocumentFilter {
boolean match(Document document);
}
如果在上面getPositiveProfileFilter的profile参数,document包含这个profile会返回true然后加载这个配置文件。addToLoaded方法是将加载的配置文件中PropertySource加入到MutablePropertySources中。
private DocumentConsumer addToLoaded(
BiConsumer<MutablePropertySources, PropertySource<?>> addMethod,
boolean checkForExisting) {
return (profile, document) -> {
if (checkForExisting) {
for (MutablePropertySources merged : this.loaded.values()) {
if (merged.contains(document.getPropertySource().getName())) {
return;
}
}
}
MutablePropertySources merged = this.loaded.computeIfAbsent(profile,
(k) -> new MutablePropertySources());
addMethod.accept(merged, document.getPropertySource());
};
}
其返回类型也是一个函数式接口DocumentConsumer,也定义在这个类下
@FunctionalInterface
private interface DocumentConsumer {
void accept(Profile profile, Document document);
}
继续调用load方法
private void load(Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
Set<String> names = (isFolder ? getSearchNames() : NO_SEARCH_NAMES);
names.forEach(
(name) -> load(location, name, profile, filterFactory, consumer));
});
}
springboot项目默认的配置文件的地址路径有四个classpath,classpath:/config, file,file:/config,默认的配置文件名称是application,循环这四个路径下找到配置文件,默认的文件类型propertise,xml,yml,ymal四中类型,对应的加载器PropertiesPropertySourceLoader和 YmalPropertySourceLoader。
// 分别用这两中加载器加载
for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String fileExtension : loader.getFileExtensions()) {
String prefix = location + name;
fileExtension = "." + fileExtension;
// 加载不同后缀
loadForFileExtension(loader, prefix, fileExtension, profile,
filterFactory, consumer);
}
}
继续调用
private void loadForFileExtension(PropertySourceLoader loader, String prefix,
String fileExtension, Profile profile,
DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
if (profile != null) {
// Try profile-specific file & profile section in profile file (gh-340)
String profileSpecificFile = prefix + "-" + profile + fileExtension;
// 加载profile为default的load
load(loader, profileSpecificFile, profile, defaultFilter, consumer);
//加载profile为其他值的load
load(loader, profileSpecificFile, profile, profileFilter, consumer);
// Try profile specific sections in files we've already processed
for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile
+ fileExtension;
load(loader, previouslyLoaded, profile, profileFilter, consumer);
}
}
}
// Also try the profile-specific section (if any) of the normal file
// 加载profile为null的load
load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
继续调用
private void load(PropertySourceLoader loader, String location, Profile profile,
DocumentFilter filter, DocumentConsumer consumer) {
try {
Resource resource = this.resourceLoader.getResource(location);
String description = getDescription(location, resource);
if (profile != null) {
description = description + " for profile " + profile;
}
if (resource == null || !resource.exists()) {
this.logger.trace("Skipped missing config " + description);
return;
}
if (!StringUtils.hasText(
StringUtils.getFilenameExtension(resource.getFilename()))) {
this.logger.trace("Skipped empty config extension " + description);
return;
}
String name = "applicationConfig: [" + location + "]";
//加载documemt文件
List<Document> documents = loadDocuments(loader, name, resource);
if (CollectionUtils.isEmpty(documents)) {
this.logger.trace("Skipped unloaded config " + description);
return;
}
List<Document> loaded = new ArrayList<>();
for (Document document : documents) {
//根据profile的不同,加载不同的documemt
if (filter.match(document)) {
addActiveProfiles(document.getActiveProfiles());
addIncludedProfiles(document.getIncludeProfiles());
loaded.add(document);
}
}
Collections.reverse(loaded);
if (!loaded.isEmpty()) {
loaded.forEach((document) -> consumer.accept(profile, document));
this.logger.debug("Loaded config file " + description);
}
}
catch (Exception ex) {
throw new IllegalStateException("Failed to load property "
+ "source from location '" + location + "'", ex);
}
}
配置文件的优先级
springboot中默认文件路径file:,file:/config,classpath:/config,classpath:/,读取文件是按照这个顺序依次读取,然后调用MutablePropertySources的addLast方法。springboot寻找配置文件时从MutablePropertySources中前面开始,所以前面的配置会覆盖后面的配置。

application.yml配置数据如下
config/application.yml
server:
port: 8080
---
/applicaiton.yml
server:
port: 9090
加载到MutablePropertySources数据如下:

在浏览器中可以看到打印出的端口为8080

Profile-多环境配置
应用程序对于不同的运行环境有不同的配置不同,application.xml的配置数据如下
server:
port: 8080
spring:
profiles:
active: dev
---
spring:
profiles: dev
name: haha
---
spring:
profiles: test
name: xixi
浏览器打印的输入如下:

加载多环境的过程如下
1.首先默认添加null和default的profile到profiles的队列中
2.从队列中取出null的profile,调用load方法,分别从四个默认的路径下寻找application的文件,调用YamlPropertySourceLoad去加载
3.加载3份document文件如下

4.此时profile为null,会加载document#0

5.profiles队列不为空,继续循环添加,此时profile为dev,会加载document#1

6.profiles队列为空,循环结束。
参考
http://fangjian0423.github.io/2017/06/10/springboot-environment-analysis/