文章目录
SpringBoot读取application.properties原理
在SpringApplication.run方法中打断点。
ApplicationEnvironmentPreparedEvent事件
spring主要在环境准备阶段广播了ApplicationEnvironmentPreparedEvent事件,然后相应的监听器进行处理。
进入到org.springframework.boot.SpringApplication#prepareEnvironment方法
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 发送ApplicationEnvironmentPreparedEvent事件
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
org.springframework.boot.context.event.EventPublishingRunListener#environmentPrepared方法广播事件
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)执行事件监听器
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
ConfigFileApplicationListener监听器
查看ConfigFileApplicationListener监听器的实现。里面对配置文件进行了解析。
里面使用了PropertySourceLoader的两个实现类PropertiesPropertySourceLoader和YamlPropertySourceLoader对文件进行解析。
/*
监听器的调用方法
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
// 如果事件是ApplicationEnvironmentPreparedEvent则去完成解析
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
/*
如果是onApplicationEnvironmentPreparedEvent事件则进入这个方法
*/
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
// 调用所有的环境后置处理器,ConfigFileApplicationListener也是一个环境后置处理器
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
/*
执行后置处理器的实现方法
*/
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
/*
添加属性源
*/
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
// 调用内部类Loader的load方法
new Loader(environment, resourceLoader).load();
}
private class Loader {
/*
被addPropertySources方法调用的load方法
*/
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());
}
// 加载配件,第三个参数为consumer接口实现,用于将加载的配置保存出来
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
});
}
/*
遍历目录,然后加载
*/
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));
});
}
/*
加载指定的name,比如bootstrap,application等
*/
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
if (!StringUtils.hasText(name)) {
for (PropertySourceLoader loader : this.propertySourceLoaders) {
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
throw new IllegalStateException("File extension of config file location '" + location
+ "' is not known to any PropertySourceLoader. If the location is meant to reference "
+ "a directory, it must end in '/'");
}
// 用来保存当前已经解析过的后缀名
Set<String> processed = new HashSet<>();
// 遍历属性源加载器,默认有两个:PropertiesPropertySourceLoader,YamlPropertySourceLoader
for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String fileExtension : loader.getFileExtensions()) {
// 保证同一个后缀名只有一个加载器加载过一次
if (processed.add(fileExtension)) {
loadForFileExtension(loader, location + name, "." + 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;
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);
}
/*
加载指定配置文件,location为文件路径,内部调用了PropertySourceLoader的load方法
*/
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
DocumentConsumer consumer) {
Resource[] resources = getResources(location);
for (Resource resource : resources) {
try {
if (resource == null || !resource.exists()) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped missing config ", location, resource,
profile);
this.logger.trace(description);
}
continue;
}
if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped empty config extension ", location,
resource, profile);
this.logger.trace(description);
}
continue;
}
String name = "applicationConfig: [" + getLocationName(location, resource) + "]";
// 内部调用PropertySourceLoader的load方法
List<Document> documents = loadDocuments(loader, name, resource);
if (CollectionUtils.isEmpty(documents)) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped unloaded config ", location, resource,
profile);
this.logger.trace(description);
}
continue;
}
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()) {
// 将解析后的配置到consumer中回调
loaded.forEach((document) -> consumer.accept(profile, document));
if (this.logger.isDebugEnabled()) {
StringBuilder description = getDescription("Loaded config file ", location, resource,
profile);
this.logger.debug(description);
}
}
}
catch (Exception ex) {
StringBuilder description = getDescription("Failed to load property source from ", location,
resource, profile);
throw new IllegalStateException(description.toString(), ex);
}
}
}
}
PropertiesPropertySourceLoader配置加载器
该加载器主要对properties和xml文件进行解析。
/*
实现PropertySourceLoader接口的load方法
*/
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
Map<String, ?> properties = loadProperties(resource);
if (properties.isEmpty()) {
return Collections.emptyList();
}
return Collections
.singletonList(new OriginTrackedMapPropertySource(name, Collections.unmodifiableMap(properties), true));
}
/*
从指定的Resource中加载配置
*/
private Map<String, ?> loadProperties(Resource resource) throws IOException {
String filename = resource.getFilename();
if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
// 加载xml配置文件
return (Map) PropertiesLoaderUtils.loadProperties(resource);
}
// 加载properties配置文件
return new OriginTrackedPropertiesLoader(resource).load();
}
OriginTrackedPropertiesLoader配置加载器
对properties配置文件进行解析。
同时也是乱码出现的地方。
/*
PropertiesPropertySourceLoader.loadProperties调用的方法
*/
Map<String, OriginTrackedValue> load() throws IOException {
return load(true);
}
/*
具体加载配置实现方法
*/
Map<String, OriginTrackedValue> load(boolean expandLists) throws IOException {
// CharacterReader 默认采用了ISO_8859_1编码造成了乱码
try (CharacterReader reader = new CharacterReader(this.resource)) {
Map<String, OriginTrackedValue> result = new LinkedHashMap<>();
StringBuilder buffer = new StringBuilder();
while (reader.read()) {
String key = loadKey(buffer, reader).trim();
if (expandLists && key.endsWith("[]")) {
key = key.substring(0, key.length() - 2);
int index = 0;
do {
OriginTrackedValue value = loadValue(buffer, reader, true);
put(result, key + "[" + (index++) + "]", value);
if (!reader.isEndOfLine()) {
reader.read();
}
}
while (!reader.isEndOfLine());
}
else {
OriginTrackedValue value = loadValue(buffer, reader, false);
put(result, key, value);
}
}
return result;
}
}
// 静态内部类
private static class CharacterReader implements Closeable {
CharacterReader(Resource resource) throws IOException {
// 默认采用了ISO_8859_1编码
this.reader = new LineNumberReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.ISO_8859_1));
}
}
application.properties中文出现乱码
因为spring中默认读取配置的编码格式为 ISO_8859_1 。
所以在properties中的中文配置会变成乱码。
解决方案1
在IDEA中勾选如上选项。
勾选的意思是将properties文件都转成ASCII码,但是在IDEA中显示为正常意思,如果用其它编辑器打开,就会变成Unicode。
并且在实际开发的时候,勾选这个选项后,自己本地查看是正常的,但其他人没有勾选这个选项,那么就会出现Unicode。
解决方案2
自定义实现PropertySourceLoader接口。
这个接口是spring用于解析配置文件的接口,查看spring的PropertiesPropertySourceLoader发现其中使用的编码为ISO_8859_1;所以才出现乱码问题。
自定义实现一个UTF-8的配置文件解析器。
1、直接将spring的PropertiesPropertySourceLoader的实现逻辑拿过来,修改其中的编码格式即可。
package com.yiweituo.fix.infrastructure.loader;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.origin.OriginTrackedValue;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 自定义 properties文件后缀加载器
* 因为spring默认的PropertiesPropertySourceLoader采用ISO_8859_1编码读取
*
* @author wenei
* @date 2021-04-10 10:56
*/
public class CustomPropertiesPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
return new String[]{"properties"};
}
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
Map<String, ?> properties = loadProperties(resource);
if (properties.isEmpty()) {
return Collections.emptyList();
}
return Collections
.singletonList(new OriginTrackedMapPropertySource(name, Collections.unmodifiableMap(properties), true));
}
private Map<String, ?> loadProperties(Resource resource) throws IOException {
// 将spring的OriginTrackedPropertiesLoader拉下来重写,修改里面的编码即可
return new OriginTrackedPropertiesLoader(resource).load();
}
}
自定义OriginTrackedPropertiesLoader类,将编码格式修改。
Map<String, OriginTrackedValue> load(boolean expandLists) throws IOException {
try (CharacterReader reader = new CharacterReader(this.resource)) {
Map<String, OriginTrackedValue> result = new LinkedHashMap<>();
StringBuilder buffer = new StringBuilder();
while (reader.read()) {
String key = loadKey(buffer, reader).trim();
if (expandLists && key.endsWith("[]")) {
key = key.substring(0, key.length() - 2);
int index = 0;
do {
OriginTrackedValue value = loadValue(buffer, reader, true);
put(result, key + "[" + (index++) + "]", value);
if (!reader.isEndOfLine()) {
reader.read();
}
}
while (!reader.isEndOfLine());
}
else {
OriginTrackedValue value = loadValue(buffer, reader, false);
put(result, key, value);
}
}
return result;
}
}
CharacterReader(Resource resource) throws IOException {
this.reader = new LineNumberReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8));
}
2、在项目中添加META-INF/spring.factories文件即可
org.springframework.boot.env.PropertySourceLoader=\
com.infrastructure.loader.CustomPropertiesPropertySourceLoader