springboot 读取application.properties流程
一、application.properties配置如下,当然也可以配置YAML。
application-dev.properties
server.port=8110
spring.application.name=newday-service
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=trace
spring.datasource.primary.jdbc-url=jdbc:mysql://rm-wz96s5izji5099uz13o.mysql.rds.aliyuncs.com:3306/xfz178_com?serverTimezone=UTC&characterEncoding=utf8
## test2 database
#spring.datasource.localdb.jdbc-url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&characterEncoding=utf8
#spring.datasource.localdb.username=root
#spring.datasource.localdb.password=123456
#spring.datasource.localdb.driverClassName=com.mysql.cj.jdbc.Driver
#logging.file.name=./logs/newday-service.log
#日志级别
#logging.file.level.ROOT=DEBUG
#logging.config=classpath:log4j2.xml
logging.config=classpath:logback-spring.xml
redisson.address = redis://127.0.0.1:6379
redisson.password =
#people.beanfactory = PeopleSpringFactory
people.beanfactory = PeopleBeanFactory
二、加载流程
1.SpringApplication在Run时,会先预加载环境,即是读取各种配置到环境对象中,然后会广播
ApplicationEnvironmentPreparedEvent消息。
org.springframework.boot.context.event.environmentPrepared
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
2.org.springframework.boot.context.config.ConfigFileApplicationListener.onApplicationEvent会被触发调用,因为其实现了ApplicationListener会接收事件。
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
3.然后会调用到ConfigFileApplicationListener.postProcessEnvironment,去加载属性配置文件到环境对象。
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
4.在ConfigFileApplicationListener.Loader的内部类中,会初始化两个资源配置文件加载器。properties,yaml
5.在loader.load方法中会初始化所有的profile,然后再搜索可用的PROFILE配置文件。
void load() {
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
(defaultProperties) -> {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
});
}
6. 第二个load方法
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
getSearchLocations().forEach((location) -> {
boolean isDirectory = location.endsWith("/");
Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
});
}
最终读取到的搜索路径为:
获取搜索文件名,这里注意,如果环境对象中有CONFIG_NAME_PROPERT(spring.config.name)这个配置属性,前面的bootStrap.properties就是通过前一个BootstrapApplicationListener加载的listener,这个对象会在监听到环境准备事件时,设置spring.config.name为bootStrap,然后由当前这个listener来加载配置,则搜索这个文件名,否则搜索默认文件名DEFAULT_NAMES(application):
private Set<String> getSearchNames() {
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
Set<String> names = asResolvedSet(property, null);
names.forEach(this::assertValidConfigName);
return names;
}
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}
7.接着调用第三个load方法,搜索不同目录下的配置文件。
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);
}
}
}
}
8.接着调用第四个load,加载指定扩展名的文件。
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;
load(loader, profileSpecificFile, profile, defaultFilter, consumer);
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
load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
9.接着调用第五个load方法,真正去目录搜索指定文件名和后缀的文件,然后加载到文档。
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
DocumentConsumer consumer) {
Resource[] resources = getResources(location);
for (Resource resource : resources) {
try {
String name = "applicationConfig: [" + getLocationName(location, resource) + "]";
List<Document> documents = loadDocuments(loader, name, resource);
List<Document> loaded = new ArrayList<>();
for (Document document : documents) {
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));
if (this.logger.isDebugEnabled()) {
StringBuilder description = getDescription("Loaded config file ", location, resource,
profile);
this.logger.debug(description);
}
}
}
}
}
10.在加载文档时最后会转换文档 。
private List<Document> asDocuments(List<PropertySource<?>> loaded) {
if (loaded == null) {
return Collections.emptyList();
}
return loaded.stream().map((propertySource) -> {
Binder binder = new Binder(ConfigurationPropertySources.from(propertySource),
this.placeholdersResolver);
String[] profiles = binder.bind("spring.profiles", STRING_ARRAY).orElse(null);
Set<Profile> activeProfiles = getProfiles(binder, ACTIVE_PROFILES_PROPERTY);
Set<Profile> includeProfiles = getProfiles(binder, INCLUDE_PROFILES_PROPERTY);
return new Document(propertySource, profiles, activeProfiles, includeProfiles);
}).collect(Collectors.toList());
}
11.getProfiles(binder, ACTIVE_PROFILES_PROPERTY)这句话,会去从当前读取到的配置文件属性中查找spring.profiles.active属性,然后设置到环境变量。
这个绑定方法和@configurationProperties中的对象属性绑定原理一致,都是从资源属性列表中逐个资源文件中搜索名称为name的配置。像这里就是spring.profile.active,spring.profile.include
private Set<Profile> getProfiles(Binder binder, String name) {
return binder.bind(name, STRING_ARRAY).map(this::asProfileSet).orElse(Collections.emptySet());
}
注意这里的binder不是当前环境变量的binder,而是当前搜索到的配置文件动态生成的BINDER.
Binder binder = new Binder(ConfigurationPropertySources.from(propertySource),
this.placeholdersResolver);
现在我们读取application.properties的配置数据了,只有一个。
12.然后我们接着最后一个load代码,当读取到默认的配置文档后,会再次加载激活和包含的profile配置。
addActiveProfiles(document.getActiveProfiles());
addIncludedProfiles(document.getIncludeProfiles());
loaded.add(document);
void addActiveProfiles(Set<Profile> profiles) {
this.profiles.addAll(profiles);
this.activatedProfiles = true;
removeUnprocessedDefaultProfiles();
}
此时我们可以看到,已经把dev的profile也加进来了。
13.最后会把加载到的文档 配置属性回调给消费者函数。
loaded.forEach((document) -> consumer.accept(profile, document));
ConfigFileApplicationListener.addToLoaded就是消费的回调。
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());
};
}
这里面的addMethod又是一个回调方法,最终调用到
MutablePropertySources.addLast将这个资源文件对象加载到环境对象的propertList属性中。
设置两个回调的地方在第一个load中,可以找到第一个看代码。
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
public void addLast(PropertySource<?> propertySource) {
synchronized (this.propertySourceList) {
removeIfPresent(propertySource);
this.propertySourceList.add(propertySource);
}
}
最终属性数据存储在内部类loader
private Map<Profile, MutablePropertySources> loaded 属性中。
14.然后我们再回到第一个load函数,他会不断循环所有的profiles,因为之前已经往这个profile列表中加上dev,所以会再次循环加载dev的profile配置,流程一样。
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
这是加载dev的profile的配置属性列表。
然后回调,就会把dev的文档生成资源属性文件添加到loaded列表。
15.最后会调用addLoadedPropertySources把加载成功的配置属性添加到环境对象的propertySources中。
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<>();
for (MutablePropertySources sources : loaded) {
for (PropertySource<?> source : sources) {
if (added.add(source.getName())) {
addLoadedPropertySource(destination, lastAdded, source);
lastAdded = source.getName();
}
}
}
}
加载前
加载后
我们可以看到,是加载到尾部,这也证明了配置文件的优先级最低。同时我们可以看到dev的配置文件是在默认之前。所以会覆盖默认的。
注意上面添加代码,有对loaded作倒序处理。
Collections.reverse(loaded);
至此,配置文件属性加载完毕。
CSDN-Ada助手: SpringBoot项目里,如何做异步计算,异步计算超时和异常处理你会怎么做呢?
CSDN-Ada助手: 推荐 Java 技能树:https://edu.csdn.net/skill/java?utm_source=AI_act_java
乘风破浪的码农: 估计不行了,要重新建新的,然后迁移数据
万分之一甜: 设置为范围分片了 还能更改为hash 吗?
disguiser_9: 改了之后还是那样呢