SpringBoot使用@Value读取.properties中文乱码源码及解决方法

问题重现

某不知名springboot小项目,application.properties文件:

custom.param=中文属性值

   
   
  • 1

java代码:

@SpringBootApplication
public class Application {
<span class="token annotation punctuation">@Value</span><span class="token punctuation">(</span><span class="token string">"${custom.param}"</span><span class="token punctuation">)</span>
<span class="token keyword">private</span> String param<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>
    SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>Application<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token annotation punctuation">@PostConstruct</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">printText</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> UnsupportedEncodingException <span class="token punctuation">{</span>
    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>param<span class="token punctuation">)</span><span class="token punctuation">;</span>
    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>param<span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span>StandardCharsets<span class="token punctuation">.</span>ISO_8859_1<span class="token punctuation">)</span><span class="token punctuation">,</span> StandardCharsets<span class="token punctuation">.</span>UTF_8<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

控制台输出:

中文属性值
中文属性值

 
 

结论

先写结论:用@Value注解读取application.properties文件时,编码默认是ISO-8859-1,所以直接配置中文一定会乱码。注意,配置文件是springboot默认的配置文件application.properties或application-{active}.properties。其他配置文件会在原因分析中进行详解,原因分析涉及大量源码解读,如果不想烧脑深入分析的话可以直接跳到解决方案一节。

写在前面

写本文时,我查了网上几乎所有关于@Value读取.properties中文乱码的文章。一种思路是修改编码格式;另外一种是利用插件/IDE将中文预先编码,在注入到变量后直接转码为所需要的中文。遇到中文乱码修改编码方式是常规思路,所以第一种思路看似没问题,但是把springboot所有关于encoding的配置参数修改为UTF-8后,中文乱码的问题依然没有解决。

原因分析

Spring Boot版本:2.1.1.RELEASE

  • 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 class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> String XML_FILE_EXTENSION <span class="token operator">=</span> <span class="token string">".xml"</span><span class="token punctuation">;</span>

<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> String<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">getFileExtensions</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token string">"properties"</span><span class="token punctuation">,</span> <span class="token string">"xml"</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> List<span class="token operator">&lt;</span>PropertySource<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">&gt;&gt;</span> <span class="token function">load</span><span class="token punctuation">(</span>String name<span class="token punctuation">,</span> Resource resource<span class="token punctuation">)</span> <span class="token keyword">throws</span> IOException <span class="token punctuation">{</span>
    Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> <span class="token operator">?</span><span class="token operator">&gt;</span> properties <span class="token operator">=</span> <span class="token function">loadProperties</span><span class="token punctuation">(</span>resource<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>properties<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> Collections<span class="token punctuation">.</span><span class="token function">emptyList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token keyword">return</span> Collections<span class="token punctuation">.</span><span class="token function">singletonList</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">OriginTrackedMapPropertySource</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> properties<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</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 class="token punctuation">;</span>
<span class="token keyword">byte</span> bom<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">byte</span><span class="token punctuation">[</span>BOM_SIZE<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">int</span> n<span class="token punctuation">,</span> unread<span class="token punctuation">;</span>
n <span class="token operator">=</span> internalIn<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>bom<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> bom<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>bom<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">)</span> <span class="token number">0xEF</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>bom<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">)</span> <span class="token number">0xBB</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>bom<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">)</span> <span class="token number">0xBF</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    encoding <span class="token operator">=</span> UTF8<span class="token punctuation">;</span>
    unread <span class="token operator">=</span> n <span class="token operator">-</span> <span class="token number">3</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>bom<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">)</span> <span class="token number">0xFE</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>bom<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">)</span> <span class="token number">0xFF</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    encoding <span class="token operator">=</span> UTF16BE<span class="token punctuation">;</span>
    unread <span class="token operator">=</span> n <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>bom<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">)</span> <span class="token number">0xFF</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>bom<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">)</span> <span class="token number">0xFE</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    encoding <span class="token operator">=</span> UTF16LE<span class="token punctuation">;</span>
    unread <span class="token operator">=</span> n <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token comment">// Unicode BOM mark not found, unread all bytes</span>
    encoding <span class="token operator">=</span> UTF8<span class="token punctuation">;</span>
    unread <span class="token operator">=</span> n<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">if</span> <span class="token punctuation">(</span>unread <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span>
    internalIn<span class="token punctuation">.</span><span class="token function">unread</span><span class="token punctuation">(</span>bom<span class="token punctuation">,</span> <span class="token punctuation">(</span>n <span class="token operator">-</span> unread<span class="token punctuation">)</span><span class="token punctuation">,</span> unread<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// Use given encoding</span>
CharsetDecoder decoder <span class="token operator">=</span> encoding<span class="token punctuation">.</span><span class="token function">newDecoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">onUnmappableCharacter</span><span class="token punctuation">(</span>
        CodingErrorAction<span class="token punctuation">.</span>REPORT<span class="token punctuation">)</span><span class="token punctuation">;</span>
internalIn2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">InputStreamReader</span><span class="token punctuation">(</span>internalIn<span class="token punctuation">,</span> decoder<span class="token punctuation">)</span><span class="token punctuation">;</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时,都是开发比较重要的参数,尽量使用英文,业务相关的中文配置还是不要放到这里。


  1. https://blog.csdn.net/m0_37995707/article/details/77506184 ‘Spring Boot自定义属性以及乱码问题’ ↩︎

                                </div>
            <link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-b6c3c6d139.css" 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="{&quot;mod&quot;:&quot;popu_824&quot;}"><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="{&quot;mod&quot;:&quot;1582594662_002&quot;}"><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="https://blog.csdn.net/formemorywithyou">
                <img src="https://profile.csdnimg.cn/9/0/2/3_formemorywithyou" class="avatar_pic" username="formemorywithyou">
                                        <img src="https://g.csdnimg.cn/static/user-reg-year/1x/8.png" class="user-years">
                                </a></div>
            <div class="middle-message">
                                    <div class="title"><span class="tit"><a href="https://blog.csdn.net/formemorywithyou" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}" 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="https://im.csdn.net/im/main.html?userName=formemorywithyou" 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="{&quot;mod&quot;:&quot;popu_379&quot;}">关注</a>
                                </div>
                        </div>
                </div>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值