springboot启动流程(四)application配置⽂件加载过程
所有⽂章
触发加载配置⽂件
在中,我们看到了Environment对象的创建⽅法。同时也稍微提及了⼀下ConfigFileApplicationListener这个,这个主要⼯作是为了加载application.properties/yml配置⽂件的。
回顾⼀下prepareEnvironment⽅法的代码
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments
) {
// 创建⼀个Environment对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置Environment对象
configureEnvironment(environment, SourceArgs());
// 触发(主要是触发ConfigFileApplicationListener,这个将会加载如application.properties/yml这样的配置⽂件)
// 省略
}
我们看到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.Environment(), SpringApplication());
}
}
我们看到,⾸先加载了Environment的后置处理器,然后经过排序以后遍历触发每个处理器。这⾥注意,ConfigFileApplicationListener本⾝也实现了EnvironmentPostProcessor接⼝,所以这⾥将会触发ConfigFileApplicationListener内部⽅法执⾏
我们跟进ConfigFileApplicationListener的postProcessEnvironment⽅法
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, ResourceLoader());
}
再跟进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.placeholdersResolver = new vironment);
// ⽂件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()) {
Name());
}
// 加载
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 : DefaultProfiles()) {
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()) {
Name());
}
/
/ 加载
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 = dsWith("/");
// 获取所有待加载的配置⽂件名
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 (ainsProperty(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() {properties在哪打开
if (ainsProperty(CONFIG_NAME_PROPERTY)) {
String property = Property(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 : FileExtensions()) {
if (processed.add(fileExtension)) {
// 加载对应扩展名的⽂件
loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
consumer);
}
}
}
}
这个load⽅法主要逻辑表明将会加载每个加载器可以⽀持的配置⽂件,在Loader初始化的时候我们获得
了两个加载器,同时每个加载器⽀持两种格式。所以这⾥的嵌套遍历中,我们将会尝试加载4种配置⽂件,如
1)application.properties
2) l
3) l
4) application.yaml
再跟进loadForFileExtension⽅法,看看具体每种的加载
private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
DocumentFilter defaultFilter = DocumentFilter(null);
DocumentFilter profileFilter = DocumentFilter(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 = Resource(location);
// 加载为Document对象
List<Document> documents = loadDocuments(loader, name, resource);
List<Document> loaded = new ArrayList<>();
// 遍历Document集合
for (Document document : documents) {
if (filter.match(document)) {
// 添加profile
ActiveProfiles());
IncludeProfiles());
loaded.add(document);
}
}
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()) {
Name());
}
// 加载
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对象。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论