Springboot 在默认配置文件 bootstrap.properties 中使用中文会导致乱码,无法识别,但是如果使用 unicode 编码方式进行书写,则可以正确识别。 如果将 .properties 换成 .yml 也可以识别中文,只有 .properties 无法正确识别。
这是因为 .properties 文件的默认读取配置是使用的 ISO_8859_1 编码,这个编码是不支持中文的。
如果不是默认配置文件,可以使用 @PropertySource 指定配置文件的编码方式,让他支持 UTF-8。
例如: @PropertySource(value = {"classpath:info.properties"}, encoding = "UTF-8")
但是默认的 bootstrap.properties 无法识别 UTF-8,查询了好多方式都无法起效。
1. Springboot 在默认配置文件加载
查询源码:
bootstrap.properties 是在 org.springframework.boot.context.config.ConfigFileApplicationListener 中被读取。
具体其实是走 postProcessEnvironment
到 addPropertySources
,然后通过 new Loader(environment, resourceLoader).load()
实现。
源码如下:
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
private void onApplicationPreparedEvent(ApplicationEvent event) {
this.logger.switchTo(ConfigFileApplicationListener.class);
addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());
}
/**
* Add config file property sources to the specified environment.
* @param environment the environment to add source to
* @param resourceLoader the resource loader
* @see #addPostProcessors(ConfigurableApplicationContext)
*/
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
然后继续跟代码
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
DocumentConsumer consumer) {
try {
Resource resource = this.resourceLoader.getResource(location);
if (resource == null || !resource.exists()) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped missing config ", location, resource,
profile);
this.logger.trace(description);
}
return;
}
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);
}
return;
}
String name = "applicationConfig: [" + location + "]";
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);
}
return;
}
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);
}
}
}
catch (Exception ex) {
throw new IllegalStateException("Failed to load property " + "source from location '" + location + "'",
ex);
}
}
进入到当前代码里面,发现实现是在 List<Document> documents = loadDocuments(loader, name, resource);
继续跟代码,发现 loader
是 this.propertySourceLoaders
遍历得到的,最终发现是 this.propertySourceLoaders
里面有一个类 org.springframework.boot.env.PropertiesPropertySourceLoader
实现的 properties 加载,查看这个类
@Override
public String[] getFileExtensions() {
return new String[] { "properties", "xml" };
}
@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, properties));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Map<String, ?> loadProperties(Resource resource) throws IOException {
String filename = resource.getFilename();
if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
return (Map) PropertiesLoaderUtils.loadProperties(resource);
}
return new OriginTrackedPropertiesLoader(resource).load();
}
实现是 return new OriginTrackedPropertiesLoader(resource).load();
进入这里面发现读取是通过 try (CharacterReader reader = new CharacterReader(this.resource)) {
,查看 CharacterReader
,发现这里面编码是写死的。
CharacterReader(Resource resource) throws IOException {
this.reader = new LineNumberReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.ISO_8859_1));
}
因为这里面是写死的,所以想要通过配置的方法来实现默认配置文件能够读取中文是无法实现的,只能重写PropertiesPropertySourceLoader
类。
2. 重写PropertiesPropertySourceLoader
类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
/**
* @author ******
*/
public class UnicodePropertySourceLoader implements PropertySourceLoader {
Logger logger = LoggerFactory.getLogger(UnicodePropertySourceLoader.class);
@Override
public String[] getFileExtensions() {
return new String[]{"properties", "xml"};
}
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
Properties properties = getProperties(resource);
if (!properties.isEmpty()) {
PropertiesPropertySource propertiesPropertySource = new PropertiesPropertySource(name, properties);
return Collections.singletonList(propertiesPropertySource);
}
return Collections.emptyList();
}
private Properties getProperties(Resource resource) {
Properties properties = new Properties();
try (InputStream inputStream = resource.getInputStream()) {
properties.load(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
} catch (IOException e) {
logger.error("Load resource inputStream failed.", e);
}
return properties;
}
}
在 resources 下新建文件夹 META-INF,然后新建文件 spring.factories,
在里面将类加入进去。
org.springframework.boot.env.PropertySourceLoader=com.znv.peim.UnicodePropertySourceLoader
3. 效果
项目结构
乱码
成功
说明:在 ApplicationRunner 下的 统一监控中心
是使用的默认配置,而这是一个神奇的中文
则是 info.properties 内配置的,是否中文显示由 @PropertySource(value = {"classpath:info.properties"}, encoding = "UTF-8")
控制