问题重现
某不知名springboot小项目,application.properties文件:
custom.param=中文属性值
java代码:
@SpringBootApplication
public class Application {
<span >@Value</span><span >(</span><span >"${custom.param}"</span><span >)</span>
<span >private</span> String param<span >;</span>
<span >public</span> <span >static</span> <span >void</span> <span >main</span><span >(</span>String<span >[</span><span >]</span> args<span >)</span> <span >throws</span> Exception <span >{</span>
SpringApplication<span >.</span><span >run</span><span >(</span>Application<span >.</span><span >class</span><span >,</span> args<span >)</span><span >;</span>
<span >}</span>
<span >@PostConstruct</span>
<span >public</span> <span >void</span> <span >printText</span><span >(</span><span >)</span> <span >throws</span> UnsupportedEncodingException <span >{</span>
System<span >.</span>out<span >.</span><span >println</span><span >(</span>param<span >)</span><span >;</span>
System<span >.</span>out<span >.</span><span >println</span><span >(</span><span >new</span> <span >String</span><span >(</span>param<span >.</span><span >getBytes</span><span >(</span>StandardCharsets<span >.</span>ISO_8859_1<span >)</span><span >,</span> StandardCharsets<span >.</span>UTF_8<span >)</span><span >)</span><span >;</span>
<span >}</span>
控制台输出:
ä¸æ–‡å±žæ€§å€¼
中文属性值
- application.properties采用ISO-8859-1加载
- 自定义test.properties可以设置编码格式
- .yml/.yaml默认采用UTF-8加载
application.properties文件加载
正如前文所述读取配置文件时,编码出现了问题。追踪一下spring boot是加载默认配置文件的过程,会发现org.springframework.boot.contex.config.ConfigFileApplicationListener类的loadDocuments()方法,源码如下:
private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource) throws IOException {
DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
List<Document> documents = this.loadDocumentsCache.get(cacheKey);
if (documents == null) {
List<PropertySource<?>> loaded = loader.load(name, resource);
documents = asDocuments(loaded);
this.loadDocumentsCache.put(cacheKey, documents);
}
return documents;
}
入参loader的类型是PropertySourceLoader,PropertySourceLoader是加载属性文件的接口,其实现有两个类:PropertiesPropertySourceLoader和YamlPropertySourceLoader。loader根据传入参数的实例调用load()方法,此处我们讨论.properties文件,接口声明和properties加载实现如下:
/*********属性文件加载接口**********/
public interface PropertySourceLoader {
String[] getFileExtensions();
List<PropertySource<?>> load(String name, Resource resource) throws IOException;
}
/*********properties文件加载实现**********/
public class PropertiesPropertySourceLoader implements PropertySourceLoader {
<span >private</span> <span >static</span> <span >final</span> String XML_FILE_EXTENSION <span >=</span> <span >".xml"</span><span >;</span>
<span >@Override</span>
<span >public</span> String<span >[</span><span >]</span> <span >getFileExtensions</span><span >(</span><span >)</span> <span >{</span>
<span >return</span> <span >new</span> <span >String</span><span >[</span><span >]</span> <span >{</span> <span >"properties"</span><span >,</span> <span >"xml"</span> <span >}</span><span >;</span>
<span >}</span>
<span >@Override</span>
<span >public</span> List<span ><</span>PropertySource<span ><</span><span >?</span><span >>></span> <span >load</span><span >(</span>String name<span >,</span> Resource resource<span >)</span> <span >throws</span> IOException <span >{</span>
Map<span ><</span>String<span >,</span> <span >?</span><span >></span> properties <span >=</span> <span >loadProperties</span><span >(</span>resource<span >)</span><span >;</span>
<span >if</span> <span >(</span>properties<span >.</span><span >isEmpty</span><span >(</span><span >)</span><span >)</span> <span >{</span>
<span >return</span> Collections<span >.</span><span >emptyList</span><span >(</span><span >)</span><span >;</span>
<span >}</span>
<span >return</span> Collections<span >.</span><span >singletonList</span><span >(</span><span >new</span> <span >OriginTrackedMapPropertySource</span><span >(</span>name<span >,</span> properties<span >)</span><span >)</span><span >;</span>
@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();
}}
通过源码分析PropertiesPropertySourceLoader并不单纯的加载.properties文件,还包含.xml文件(似乎有违单一功能原则,不知道当初这样设计的初衷是啥)。顺着load()方法向下找->loadProperties(Resource)->OriginTrackedPropertiesLoader.load()->OriginTrackedPropertiesLoader.load(boolean)->OriginTrackedPropertiesLoader$CharacterReader(Resource)。
CharacterReader是OriginTrackedPropertiesLoader的内部静态类,而且只有一个构造函数,看看器构造参数就不难发现为啥application.properties是以ISO-8859-1编码加载的了:
private static class CharacterReader implements Closeable {
// 其他代码省略
CharacterReader(Resource resource) throws IOException {
this.reader = new LineNumberReader(new InputStreamReader(
resource.getInputStream(), StandardCharsets.ISO_8859_1));
}
// 其他代码省略
}
也就是说不论application.properties文件被设置为哪种编码格式,最终还是以ISO-8859-1的编码格式进行加载。
yml/yaml默认以UTF-8加载
让我们再看看yml/yaml格式的文件,其加载由PropertySourceLoader接口的另外一个实例YamlPropertySourceLoader实现,即接口方法load():
List<PropertySource<?>> load(String name, Resource resource) throws IOException;
追一下load()的底层实现,采用org.yaml.snakeyaml.reader.UnicodeReader的实例对yml/ymal文件进行加载,而UnicodeReader实例对文件的初始化方法init()实现如下:
protected void init() throws IOException {
if (internalIn2 != null)
return;
Charset encoding<span >;</span>
<span >byte</span> bom<span >[</span><span >]</span> <span >=</span> <span >new</span> <span >byte</span><span >[</span>BOM_SIZE<span >]</span><span >;</span>
<span >int</span> n<span >,</span> unread<span >;</span>
n <span >=</span> internalIn<span >.</span><span >read</span><span >(</span>bom<span >,</span> <span class="token number">0</span><span >,</span> bom<span >.</span>length<span >)</span><span >;</span>
<span >if</span> <span >(</span><span >(</span>bom<span >[</span><span class="token number">0</span><span >]</span> <span >==</span> <span >(</span><span >byte</span><span >)</span> <span class="token number">0xEF</span><span >)</span> <span >&&</span> <span >(</span>bom<span >[</span><span class="token number">1</span><span >]</span> <span >==</span> <span >(</span><span >byte</span><span >)</span> <span class="token number">0xBB</span><span >)</span> <span >&&</span> <span >(</span>bom<span >[</span><span class="token number">2</span><span >]</span> <span >==</span> <span >(</span><span >byte</span><span >)</span> <span class="token number">0xBF</span><span >)</span><span >)</span> <span >{</span>
encoding <span >=</span> UTF8<span >;</span>
unread <span >=</span> n <span >-</span> <span class="token number">3</span><span >;</span>
<span >}</span> <span >else</span> <span >if</span> <span >(</span><span >(</span>bom<span >[</span><span class="token number">0</span><span >]</span> <span >==</span> <span >(</span><span >byte</span><span >)</span> <span class="token number">0xFE</span><span >)</span> <span >&&</span> <span >(</span>bom<span >[</span><span class="token number">1</span><span >]</span> <span >==</span> <span >(</span><span >byte</span><span >)</span> <span class="token number">0xFF</span><span >)</span><span >)</span> <span >{</span>
encoding <span >=</span> UTF16BE<span >;</span>
unread <span >=</span> n <span >-</span> <span class="token number">2</span><span >;</span>
<span >}</span> <span >else</span> <span >if</span> <span >(</span><span >(</span>bom<span >[</span><span class="token number">0</span><span >]</span> <span >==</span> <span >(</span><span >byte</span><span >)</span> <span class="token number">0xFF</span><span >)</span> <span >&&</span> <span >(</span>bom<span >[</span><span class="token number">1</span><span >]</span> <span >==</span> <span >(</span><span >byte</span><span >)</span> <span class="token number">0xFE</span><span >)</span><span >)</span> <span >{</span>
encoding <span >=</span> UTF16LE<span >;</span>
unread <span >=</span> n <span >-</span> <span class="token number">2</span><span >;</span>
<span >}</span> <span >else</span> <span >{</span>
<span >// Unicode BOM mark not found, unread all bytes</span>
encoding <span >=</span> UTF8<span >;</span>
unread <span >=</span> n<span >;</span>
<span >}</span>
<span >if</span> <span >(</span>unread <span >></span> <span class="token number">0</span><span >)</span>
internalIn<span >.</span><span >unread</span><span >(</span>bom<span >,</span> <span >(</span>n <span >-</span> unread<span >)</span><span >,</span> unread<span >)</span><span >;</span>
<span >// Use given encoding</span>
CharsetDecoder decoder <span >=</span> encoding<span >.</span><span >newDecoder</span><span >(</span><span >)</span><span >.</span><span >onUnmappableCharacter</span><span >(</span>
CodingErrorAction<span >.</span>REPORT<span >)</span><span >;</span>
internalIn2 <span >=</span> <span >new</span> <span >InputStreamReader</span><span >(</span>internalIn<span >,</span> decoder<span >)</span><span >;</span>
每次调用read()读文件时都会调用init()方法进行初始化,也就是这个时候确定文件的编码格式。首先读取BOM(Byte Order Mark)文件头信息,如果头信息中有UTF8/UTF16BE/UTF16LE就采用对应的编码,没有或者不是则采用UTF8编码。
自定义test.properties文件编码
采用@PropertySource(value=“classpath:test.properties”, encoding=“UTF-8”)方式读取配置文件可按照UTF-8的方式读取编码,而不是ISO-8859-1。@PropertySource配置的加载文件由ConfigurationClassParser.processPropertySource()进行解析,EncodedResource类决定最后由哪种编码格式加载文件,其方法如下:
public Reader getReader() throws IOException {
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
}
else if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
}
else {
return new InputStreamReader(this.resource.getInputStream());
}
}
所以,虽然都是.properties文件,但是编码格式却是不一样的。
解决方案
- 自定义配置文件
- 使用yml/yaml配置文件
- IDE/插件预编码
自定义配置文件
通过@PropertySource(value=“classpath:my.properties”, encoding=“UTF-8”)注解配置自定义文件,注意文件名不能是springboot默认的application.properties文件名称。
使用yml/yaml配置文件
将yml/yaml文件设置为UTF-8的编码格式,springboot读该文件即采用UTF-8编码。
IDE/插件预编码
采用编译器或者插件将配置文件预编码。这种方法我没试过,但是想想也知道这是很反人类的。如果有人感兴趣的话,可以参考一下[这篇博客最后一部分IDEA/eclipse的修改操作]1。
总结
在配置application.properties时,都是开发比较重要的参数,尽量使用英文,业务相关的中文配置还是不要放到这里。
- ‘Spring Boot自定义属性以及乱码问题’ ↩︎
</div>
<link href="" rel="stylesheet">
<div class="more-toolbox">
<div class="left-toolbox">
<ul class="toolbox-list">
<li class="tool-item tool-active is-like "><a href="javascript:;"><svg class="icon" aria-hidden="true">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#csdnc-thumbsup"></use>
</svg><span class="name">点赞</span>
<span class="count">2</span>
</a></li>
<li class="tool-item tool-active is-collection "><a href="javascript:;" data-report-click="{"mod":"popu_824"}"><svg class="icon" aria-hidden="true">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#icon-csdnc-Collection-G"></use>
</svg><span class="name">收藏</span></a></li>
<li class="tool-item tool-active is-share"><a href="javascript:;" data-report-click="{"mod":"1582594662_002"}"><svg class="icon" aria-hidden="true">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#icon-csdnc-fenxiang"></use>
</svg>分享</a></li>
<!--打赏开始-->
<!--打赏结束-->
<li class="tool-item tool-more">
<a>
<svg t="1575545411852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M179.176 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5718"></path><path d="M509.684 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5719"></path><path d="M846.175 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5720"></path></svg>
</a>
<ul class="more-box">
<li class="item"><a class="article-report">文章举报</a></li>
</ul>
</li>
</ul>
</div>
</div>
<div class="person-messagebox">
<div class="left-message"><a href="">
<img src="" class="avatar_pic" username="formemorywithyou">
<img src="" class="user-years">
</a></div>
<div class="middle-message">
<div class="title"><span class="tit"><a href="" data-report-click="{"mod":"popu_379"}" target="_blank">EricZeng05</a></span>
</div>
<div class="text"><span>发布了20 篇原创文章</span> · <span>获赞 13</span> · <span>访问量 5332</span></div>
</div>
<div class="right-message">
<a href="" target="_blank" class="btn btn-sm btn-red-hollow bt-button personal-letter">私信
</a>
<a class="btn btn-sm bt-button personal-watch" data-report-click="{"mod":"popu_379"}">关注</a>
</div>
</div>
</div>
问题重现
某不知名springboot小项目,application.properties文件:
custom.param=中文属性值
java代码:
@SpringBootApplication
public class Application {
<span >@Value</span><span >(</span><span >"${custom.param}"</span><span >)</span>
<span >private</span> String param<span >;</span>
<span >public</span> <span >static</span> <span >void</span> <span >main</span><span >(</span>String<span >[</span><span >]</span> args<span >)</span> <span >throws</span> Exception <span >{</span>
SpringApplication<span >.</span><span >run</span><span >(</span>Application<span >.</span><span >class</span><span >,</span> args<span >)</span><span >;</span>
<span >}</span>
<span >@PostConstruct</span>
<span >public</span> <span >void</span> <span >printText</span><span >(</span><span >)</span> <span >throws</span> UnsupportedEncodingException <span >{</span>
System<span >.</span>out<span >.</span><span >println</span><span >(</span>param<span >)</span><span >;</span>
System<span >.</span>out<span >.</span><span >println</span><span >(</span><span >new</span> <span >String</span><span >(</span>param<span >.</span><span >getBytes</span><span >(</span>StandardCharsets<span >.</span>ISO_8859_1<span >)</span><span >,</span> StandardCharsets<span >.</span>UTF_8<span >)</span><span >)</span><span >;</span>
<span >}</span>