springboot启动流程(四)application配置文件加载过程
所有文章
https://www.cnblogs.com/lay2017/p/11478237.html
触发监听器加载配置文件
在上一篇文章中,我们看到了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<EnvironmentPostProcessor> 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<Profile> 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<String> 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<String> getSearchLocations() { if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { return getSearchLocations(CONFIG_LOCATION_PROPERTY); } Set<String> 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<String> 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<String> 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.properties
2) application.xml
3) application.yml
4) 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<Document> documents = loadDocuments(loader, name, resource); List<Document> 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<Profile, MutablePropertySources> loaded; private DocumentConsumer addToLoaded( BiConsumer<MutablePropertySources, PropertySource<?>> 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<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values()); // 反向排序 Collections.reverse(loaded); String lastAdded = null; Set<String> 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<Document> documents = loadDocuments(loader, name, resource); List<Document> 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<Profile> profiles; void addActiveProfiles(Set<Profile> 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", "test"]。
再看最后一行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当中。
至于具体的细节如:加载文件的时候编码相关、多个文件相同配置是否覆盖、加载器如何解析各种配置文件的内容有时间也可以仔细阅读。