当前位置:   article > 正文

SpringBoot使用@Value读取.properties中文乱码及解决方法_properties 文件value +号没有

properties 文件value +号没有

文章转自 https://blog.csdn.net/formemorywithyou/article/details/96473169

 

问题重现

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

custom.param=中文属性值

java代码:

  1. @SpringBootApplication
  2. public class Application {
  3.     @Value("${custom.param}")
  4.     private String param;
  5.     public static void main(String[] args) throws Exception {
  6.         SpringApplication.run(Application.class, args);
  7.     }
  8.     @PostConstruct
  9.     public void printText() throws UnsupportedEncodingException {
  10.         System.out.println(param);
  11.         System.out.println(new String(param.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
  12.     }
  13. }

控制台输出:

中文属性值
中文属性值


结论


先写结论:用@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()方法,源码如下:

  1. private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource) throws IOException {
  2.     DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
  3.     List<Document> documents = this.loadDocumentsCache.get(cacheKey);
  4.     if (documents == null) {
  5.         List<PropertySource<?>> loaded = loader.load(name, resource);
  6.         documents = asDocuments(loaded);
  7.         this.loadDocumentsCache.put(cacheKey, documents);
  8.     }
  9.     return documents;
  10. }


入参loader的类型是PropertySourceLoader,PropertySourceLoader是加载属性文件的接口,其实现有两个类:PropertiesPropertySourceLoader和YamlPropertySourceLoader。loader根据传入参数的实例调用load()方法,此处我们讨论.properties文件,接口声明和properties加载实现如下:

  1. /*********属性文件加载接口**********/
  2. public interface PropertySourceLoader {
  3.     String[] getFileExtensions();
  4.     List<PropertySource<?>> load(String name, Resource resource) throws IOException;
  5. }
  6. /*********properties文件加载实现**********/
  7. public class PropertiesPropertySourceLoader implements PropertySourceLoader {
  8.     private static final String XML_FILE_EXTENSION = ".xml";
  9.     @Override
  10.     public String[] getFileExtensions() {
  11.         return new String[] { "properties", "xml" };
  12.     }
  13.     @Override
  14.     public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
  15.         Map<String, ?> properties = loadProperties(resource);
  16.         if (properties.isEmpty()) {
  17.             return Collections.emptyList();
  18.         }
  19.       return Collections.singletonList(new OriginTrackedMapPropertySource(name, properties));
  20.   }
  21.   @SuppressWarnings({ "unchecked", "rawtypes" })
  22.   private Map<String, ?> loadProperties(Resource resource) throws IOException {
  23.       String filename = resource.getFilename();
  24.       if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
  25.           return (Map) PropertiesLoaderUtils.loadProperties(resource);
  26.       }
  27.       return new OriginTrackedPropertiesLoader(resource).load();
  28.   }
  29. }


通过源码分析PropertiesPropertySourceLoader并不单纯的加载.properties文件,还包含.xml文件(似乎有违单一功能原则,不知道当初这样设计的初衷是啥)。顺着load()方法向下找->loadProperties(Resource)->OriginTrackedPropertiesLoader.load()->OriginTrackedPropertiesLoader.load(boolean)->OriginTrackedPropertiesLoader$CharacterReader(Resource)。
CharacterReader是OriginTrackedPropertiesLoader的内部静态类,而且只有一个构造函数,看看器构造参数就不难发现为啥application.properties是以ISO-8859-1编码加载的了:

  1. private static class CharacterReader implements Closeable {
  2.     // 其他代码省略
  3.     CharacterReader(Resource resource) throws IOException {
  4.       this.reader = new LineNumberReader(new InputStreamReader(
  5.           resource.getInputStream(), StandardCharsets.ISO_8859_1));
  6.     }
  7.     // 其他代码省略
  8. }

也就是说不论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()实现如下:

  1. protected void init() throws IOException {
  2.     if (internalIn2 != null)
  3.         return;
  4.     Charset encoding;
  5.     byte bom[] = new byte[BOM_SIZE];
  6.     int n, unread;
  7.     n = internalIn.read(bom, 0, bom.length);
  8.     if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB) && (bom[2] == (byte) 0xBF)) {
  9.         encoding = UTF8;
  10.         unread = n - 3;
  11.     } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) 0xFF)) {
  12.         encoding = UTF16BE;
  13.         unread = n - 2;
  14.     } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE)) {
  15.         encoding = UTF16LE;
  16.         unread = n - 2;
  17.     } else {
  18.         // Unicode BOM mark not found, unread all bytes
  19.         encoding = UTF8;
  20.         unread = n;
  21.     }
  22.     if (unread > 0)
  23.         internalIn.unread(bom, (n - unread), unread);
  24.     // Use given encoding
  25.     CharsetDecoder decoder = encoding.newDecoder().onUnmappableCharacter(
  26.             CodingErrorAction.REPORT);
  27.     internalIn2 = new InputStreamReader(internalIn, decoder);
  28. }


每次调用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类决定最后由哪种编码格式加载文件,其方法如下:

  1. public Reader getReader() throws IOException {
  2.     if (this.charset != null) {
  3.       return new InputStreamReader(this.resource.getInputStream(), this.charset);
  4.     }
  5.     else if (this.encoding != null) {
  6.       return new InputStreamReader(this.resource.getInputStream(), this.encoding);
  7.     }
  8.     else {
  9.       return new InputStreamReader(this.resource.getInputStream());
  10.     }
  11.   }

所以,虽然都是.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时,都是开发比较重要的参数,尽量使用英文,业务相关的中文配置还是不要放到这里。

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/939218
推荐阅读
相关标签
  

闽ICP备14008679号