您好,欢迎来到飒榕旅游知识分享网。
搜索
您的当前位置:首页springboot启动流程(四)application配置文件加载过程

springboot启动流程(四)application配置文件加载过程

来源:飒榕旅游知识分享网
springboot启动流程(四)application配置⽂件加载过程

所有⽂章

触发加载配置⽂件

在中,我们看到了Environment对象的创建⽅法。同时也稍微提及了⼀下ConfigFileApplicationListener这个,这个主要⼯作是为了加载application.properties/yml配置⽂件的。回顾⼀下prepareEnvironment⽅法的代码

private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments ) {

// 创建⼀个Environment对象

ConfigurableEnvironment environment = getOrCreateEnvironment(); // 配置Environment对象

configureEnvironment(environment, applicationArguments.getSourceArgs());

// 触发(主要是触发ConfigFileApplicationListener,这个将会加载如application.properties/yml这样的配置⽂件) listeners.environmentPrepared(environment); // 省略}

我们看到Environment对象在初始创建并配置之后会发布出⼀个事件给,注意!这⾥的并不是ConfigFileApplicationListener⽽是⼀个负责分发事件的EventPublishingRunListener。

我们跟进EventPublishingRunListener的environmentPrepared⽅法

private final SimpleApplicationEventMulticaster initialMulticaster;

@Override

public void environmentPrepared(ConfigurableEnvironment environment) { this.initialMulticaster

.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));}

这⾥包装了⼀个ApplicationEnvironmentPreparedEvent事件,并通过⼴播的⽅式⼴播给监听该事件的,到这个时候才触发了ConfigFileApplicationListener

我们跟进ConfigFileApplicationListener的onApplicationEvent⽅法

@Override

public void onApplicationEvent(ApplicationEvent event) { // 只触发Environment相关的事件

if (event instanceof ApplicationEnvironmentPreparedEvent) {

onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); }

if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); }}

Event将会触发onApplicationEnvironmentPreparedEvent 继续跟进

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { List postProcessors = loadPostProcessors(); postProcessors.add(this);

AnnotationAwareOrderComparator.sort(postProcessors);

for (EnvironmentPostProcessor postProcessor : postProcessors) { // 执⾏后置处理器

postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); }}

我们看到,⾸先加载了Environment的后置处理器,然后经过排序以后遍历触发每个处理器。这⾥注意,ConfigFileApplicationListener本⾝也实现了EnvironmentPostProcessor接⼝,所以这⾥将会触发ConfigFileApplicationListener内部⽅法执⾏

我们跟进ConfigFileApplicationListener的postProcessEnvironment⽅法

@Override

public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { addPropertySources(environment, application.getResourceLoader());}

再跟进addPropertySources⽅法

protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); new Loader(environment, resourceLoader).load();}

我们看到,这⾥实例化了⼀个Loader⽤来加载application配置⽂件,⽽核⼼逻辑就在load⽅法当中。

加载器加载application配置⽂件

跟进Loader加载器的构造⽅法中

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { this.environment = environment;

this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);

this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(); // ⽂件application配置⽂件的资源加载器,包括propertis/xml/yml/yaml扩展名

this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader());}

我们注意到,再构造⽅法中将会从spring.factories中加载PropertySourceLoader接⼝的具体实现类,具体请参阅:。我们打开spring.factories可以看到

这⾥包括两个实现

1)PropertiesPropertySourceLoader:⽤于加载property/xml格式的配置⽂件2) YamlPropertySourceLoader:⽤于加载yml/yaml格式的配置⽂件

到这⾥,我们可以知道springboot⽀持的不同配置⽂件是通过选择不同的加载器来实现

下⾯,我们回到Loader加载器的load⽅法中,跟进加载的主要逻辑

public void load() {

this.profiles = new LinkedList<>();

this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false;

this.loaded = new LinkedHashMap<>(); // 初始化profiles initializeProfiles();

while (!this.profiles.isEmpty()) { // 消费⼀个profile

Profile profile = this.profiles.poll(); // active的profile添加到Environment

if (profile != null && !profile.isDefaultProfile()) { addProfileToEnvironment(profile.getName()); }

// 加载

load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); }

// 重置Environment中的profiles

resetEnvironmentProfiles(this.processedProfiles); // 加载

load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); // 添加所有properties到Environment中 addLoadedPropertySources();}

代码有点⼩长,我们根据如何加载默认的application.properties/yml配置⽂件的流程来了解⼀下

先跟进initializeProfiles⽅法看看如果初始化profiles

private void initializeProfiles() {

// 第⼀个profile为null,这样能保证⾸个加载application.properties/yml this.profiles.add(null);

Set activatedViaProperty = getProfilesActivatedViaProperty(); this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty)); addActiveProfiles(activatedViaProperty); // 没有额外配置profile的时候,将使⽤默认的 if (this.profiles.size() == 1) {

for (String defaultProfileName : this.environment.getDefaultProfiles()) { Profile defaultProfile = new Profile(defaultProfileName, true); this.profiles.add(defaultProfile); } }}

这⾥注意两点

1)将会⾸先添加⼀个null,保证第⼀次加载的是application配置

2) 其次,如果没有配置profile,那么使⽤default。注意,我们的application配置⽂件还未加载,所以这⾥的\"没有配置\"并不是指你的application配置⽂件中有没有配置,⽽是如命令⾏、获取main⽅法传⼊等其它⽅法配置我们并未配置任何active的profile,所以这⾥最终将产⽣⼀个这样的数据profiles=[null, \"default\"]

回到load⽅法中,我们继续往下看

public void load() {

this.profiles = new LinkedList<>();

this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false;

this.loaded = new LinkedHashMap<>(); // 初始化profiles initializeProfiles();

while (!this.profiles.isEmpty()) { // 消费⼀个profile

Profile profile = this.profiles.poll(); // active的profile添加到Environment

if (profile != null && !profile.isDefaultProfile()) { addProfileToEnvironment(profile.getName()); }

// 加载

load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); }

// 重置Environment中的profiles

resetEnvironmentProfiles(this.processedProfiles); // 加载

load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); // 添加所有properties到Environment中 addLoadedPropertySources();}

while循环中,⾸先拿到的是profile=null,然后就直接进⼊第⼆个load加载⽅法加载配置⽂件

我们跟进第⼆个load加载⽅法(请注意区分load⽅法,后续还会出现load⽅法,我们以出现的顺序区分)

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { // 获取并遍历所有待搜索的位置

getSearchLocations().forEach((location) -> { boolean isFolder = location.endsWith(\"/\"); // 获取所有待加载的配置⽂件名

Set names = isFolder ? getSearchNames() : NO_SEARCH_NAMES; // 加载每个位置的每个⽂件

names.forEach((name) -> load(location, name, profile, filterFactory, consumer)); });}

该⽅法中的逻辑主要是搜索每个位置下的每个指定的配置⽂件名,并加载

跟进getSearchLocations看看要搜索哪些位置

private static final String DEFAULT_SEARCH_LOCATIONS = \"classpath:/,classpath:/config/,file:./,file:./config/\";private Set getSearchLocations() {

if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { return getSearchLocations(CONFIG_LOCATION_PROPERTY); }

Set locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY); locations.addAll(

asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS)); return locations;}

很显然,由于我们没有⾃定义⼀些搜索位置,那么默认搜索classpath:/、classpath:/config/、file:./、file:./下

回到第⼆个load⽅法,我们再看看getSearchNames⽅法要加载哪些⽂件

private static final String DEFAULT_NAMES = \"application\";

private Set getSearchNames() {

if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {

String property = this.environment.getProperty(CONFIG_NAME_PROPERTY); return asResolvedSet(property, null); }

return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);}

相似的逻辑,最终返回默认的配置⽂件名application,也就是我们最熟悉的名字

接下来,再回到第⼆个load⽅法,我们可以跟进第三个load⽅法了,看看如何根据locations和names来加载配置⽂件

private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { // 省略

Set processed = new HashSet<>(); // 遍历加载器

for (PropertySourceLoader loader : this.propertySourceLoaders) { // 获取扩展名

for (String fileExtension : loader.getFileExtensions()) { if (processed.add(fileExtension)) { // 加载对应扩展名的⽂件

loadForFileExtension(loader, location + name, \".\" + fileExtension, profile, filterFactory, consumer); } } }}

这个load⽅法主要逻辑表明将会加载每个加载器可以⽀持的配置⽂件,在Loader初始化的时候我们获得了两个加载器,同时每个加载器⽀持两种格式。所以这⾥的嵌套遍历中,我们将会尝试加载4种配置⽂件,如1)application.properties2) application.xml3) application.yml4) application.yaml

再跟进loadForFileExtension⽅法,看看具体每种的加载

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); // 当前没有profile if (profile != null) {

String profileSpecificFile = prefix + \"-\" + profile + fileExtension; load(loader, profileSpecificFile, profile, defaultFilter, consumer); load(loader, profileSpecificFile, profile, profileFilter, consumer); for (Profile processedProfile : this.processedProfiles) { if (processedProfile != null) {

String previouslyLoaded = prefix + \"-\" + processedProfile + fileExtension; load(loader, previouslyLoaded, profile, profileFilter, consumer); } } }

// 加载具体格式的⽂件

load(loader, prefix + fileExtension, profile, profileFilter, consumer);}

由于当前profile=null,所以我们直接进⼊第四个load⽅法

跟进第四个load⽅法,由于该⽅法有点长,我们省略次要的代码

private void load(

PropertySourceLoader loader, String location, Profile profile,

DocumentFilter filter,

DocumentConsumer consumer ) { try {

// 获取资源

Resource resource = this.resourceLoader.getResource(location);

// 加载为Document对象

List documents = loadDocuments(loader, name, resource);

List loaded = new ArrayList<>(); // 遍历Document集合

for (Document document : documents) { if (filter.match(document)) { // 添加profile

addActiveProfiles(document.getActiveProfiles()); addIncludedProfiles(document.getIncludeProfiles()); loaded.add(document); } }

Collections.reverse(loaded); if (!loaded.isEmpty()) {

// 回调处理每个document

loaded.forEach((document) -> consumer.accept(profile, document)); }

} catch (Exception ex) {}}

⾸先配置⽂件会被加载为Document这样的内存对象,并最终回调处理。

这⾥我们看到回调是调⽤consumer这样⼀个接⼝,我们得回到第⼀个load⽅法,看看调⽤第⼆个load⽅法的时候传⼊的consumer是啥

public void load() {

this.profiles = new LinkedList<>();

this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false;

this.loaded = new LinkedHashMap<>(); // 初始化profiles initializeProfiles();

while (!this.profiles.isEmpty()) { // 消费⼀个profile

Profile profile = this.profiles.poll(); // active的profile添加到Environment

if (profile != null && !profile.isDefaultProfile()) { addProfileToEnvironment(profile.getName()); }

// 加载

load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); }

// 重置Environment中的profiles

resetEnvironmentProfiles(this.processedProfiles); // 加载

load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); // 添加所有properties到Environment中 addLoadedPropertySources();}

我们看到,在调⽤第⼆个load⽅法的时候就通过addToLoaded这个⽅法的执⾏来获取⼀个consumer,⽤来回调处理配置⽂件的Document对象。

注意!在调⽤addToLoaded的时候通过⽅法引⽤指定了⼀个method,这个method将在consumer回调的内部被使⽤。method=MutablePropertySources:addLast

跟进addToLoaded⽅法

private Map loaded;

private DocumentConsumer addToLoaded( BiConsumer> addMethod, boolean checkForExisting) { return (profile, document) -> { // 省略

MutablePropertySources merged = this.loaded.computeIfAbsent(profile, (k) -> new MutablePropertySources()); // 回调method

addMethod.accept(merged, document.getPropertySource()); };}

我们看到,loaded是⼀个profile和MutableProperySources的键值组合。⽅法逻辑中将会先获取loaded⾥⾯的MutablePropertySources,然后调⽤addLast⽅法将Document中的PropertySource给添加到MutablePropertySources中。

到这⾥,⼀个application配置⽂件被加载到内存了。但是还没完,前⾯的⽂章中我们说过Environment对象是应⽤程序环境的抽象,包含了properties。那么,我们还得将这些内存中的PropertySource给添加到Environment中。

回到第⼀个load⽅法中

public void load() {

this.profiles = new LinkedList<>();

this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false;

this.loaded = new LinkedHashMap<>(); // 初始化profiles initializeProfiles();

while (!this.profiles.isEmpty()) { // 消费⼀个profile

Profile profile = this.profiles.poll(); // active的profile添加到Environment

if (profile != null && !profile.isDefaultProfile()) { addProfileToEnvironment(profile.getName()); }

// 加载

load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); }

// 重置Environment中的profiles

resetEnvironmentProfiles(this.processedProfiles); // 加载

load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); // 添加所有properties到Environment中 addLoadedPropertySources();}

我们看到最后⼀⾏,addLoadedPropertySources⽅法的作⽤也就是将之前loaded⾥⾯的东西给添加到Environment中

跟进addLoadedPropertySources⽅法

private void addLoadedPropertySources() {

MutablePropertySources destination = this.environment.getPropertySources(); List loaded = new ArrayList<>(this.loaded.values()); // 反向排序

Collections.reverse(loaded); String lastAdded = null;

Set added = new HashSet<>(); // 遍历loaded

for (MutablePropertySources sources : loaded) { // 遍历配置

for (PropertySource source : sources) { // 排重

if (added.add(source.getName())) { // 添加每个到Environment中

addLoadedPropertySource(destination, lastAdded, source); lastAdded = source.getName(); }

} }}

这个⽅法很清晰地表明,将loaded中地PropertySource给追加到Environment中。

到这⾥,默认的application.properties/yml这样地配置⽂件就被加载到了Environment当中了。不过还没有结束,这⾥还有个⽐较重要的问题,多环境的时候application.properties/yml配置⽂件中指定了profile,就会加载application-{profile}.properties/yml是怎么实现的呢?

加载多环境的application-{profile}配置⽂件

回到我们之前的第四个load⽅法

private void load(

PropertySourceLoader loader, String location, Profile profile,

DocumentFilter filter,

DocumentConsumer consumer ) { try {

// 获取资源

Resource resource = this.resourceLoader.getResource(location);

// 加载为Document对象

List documents = loadDocuments(loader, name, resource);

List loaded = new ArrayList<>(); // 遍历Document集合

for (Document document : documents) { if (filter.match(document)) { // 添加profile

addActiveProfiles(document.getActiveProfiles()); addIncludedProfiles(document.getIncludeProfiles()); loaded.add(document); } }

Collections.reverse(loaded); if (!loaded.isEmpty()) {

// 回调处理每个document

loaded.forEach((document) -> consumer.accept(profile, document)); }

} catch (Exception ex) {}}

这⾥,已经把默认的application.properties/yml给加载成为了Document。然后在遍历documents的时候,会把Document中的profiles做⼀次添加

我们跟进addActiveProfiles看看

private Deque profiles;

void addActiveProfiles(Set profiles) { if (profiles.isEmpty()) { return; }

// 省略

// 添加到队列

this.profiles.addAll(profiles); // 省略

this.activatedProfiles = true; // 移除掉default

removeUnprocessedDefaultProfiles();}

我们看到,新的profiles⾸先会被添加到现有队列中。最初的profiles=[null, \"default\"]。⽽后,我们消费了null,profiles=[\"default\"]。现在,我们添加⼀个profile=\"test\"。那么,profiles=[\"default\。

再看最后⼀⾏removeUnprocessedDefaultProfiles,将会移除default。所以,最终profiles=[\"test\"]。

再回到第⼀个load⽅法中

public void load() {

this.profiles = new LinkedList<>();

this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false;

this.loaded = new LinkedHashMap<>(); // 初始化profiles initializeProfiles();

while (!this.profiles.isEmpty()) { // 消费⼀个profile

Profile profile = this.profiles.poll(); // active的profile添加到Environment

if (profile != null && !profile.isDefaultProfile()) { addProfileToEnvironment(profile.getName()); }

// 加载

load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); }

// 重置Environment中的profiles

resetEnvironmentProfiles(this.processedProfiles); // 加载

load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); // 添加所有properties到Environment中 addLoadedPropertySources();}

这时候while循环⾥⾯将会拿到profile=\"test\",跟之前⼀样⼀路下去,直到loadForFileExtension⽅法

跟进loadForFileExtension

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) {

// 拼接如:application-test.properties/yml

String profileSpecificFile = prefix + \"-\" + profile + fileExtension; // 加载⽂件

load(loader, profileSpecificFile, profile, defaultFilter, consumer); load(loader, profileSpecificFile, profile, profileFilter, consumer); for (Profile processedProfile : this.processedProfiles) { if (processedProfile != null) {

String previouslyLoaded = prefix + \"-\" + processedProfile + fileExtension; load(loader, previouslyLoaded, profile, profileFilter, consumer); } } }

// 加载具体格式的⽂件

load(loader, prefix + fileExtension, profile, profileFilter, consumer);}

我们看到,当有profile的时候⽂件名就不再是application.properties/yml了。它会把profile给拼接上去,所以就变成了application-test.properties/yml,并加载⽂件。后续也⼀样得最终添加到Environment当中。

总结

application配置⽂件的加载过程逻辑并不复杂,只是具体细节⽐较多,所以代码中包含了不少附加的逻辑。那么抛开细节,我们可以看到其实就是到相应的⽬录下搜索相应的⽂件是否存在,加载到内存以后再添加到Environment当中。

⾄于具体的细节如:加载⽂件的时候编码相关、多个⽂件相同配置是否覆盖、加载器如何解析各种配置⽂件的内容有时间也可以仔细阅读。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- sarr.cn 版权所有 赣ICP备2024042794号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务