多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

SpringBoot读取properties中文乱码根因分析及五种解决方案

SpringBoot读取properties中文乱码根因分析及五种解决方案 SpringBoot读取properties中文乱码这问题看着小真踩上的时候能把人折腾到怀疑人生。尤其是项目里配置文件一多突然某个环境的提示信息变成了一堆“锟斤拷”或者“??”第一反应往往是“代码写错了”结果查了半天发现根本不是代码问题就是编码链路某个环节断了。这篇文章我就把这个问题的来龙去脉、根因分析和可落地的解决方案完整梳理一遍按步骤操作基本能一次解决顺便把我自己踩过的坑也交代清楚免得你再走弯路。1. 乱码问题的来龙去脉1.1 为什么properties文件特别容易出中文乱码先说个基本事实properties文件本质上就是一份纯文本文件它的编码方式完全取决于你用什么工具、在什么系统环境下创建和保存的。Windows下很多编辑器默认保存为GBKLinux/macOS下默认是UTF-8而Java的Properties类在读取文件时历史上默认使用的是ISO-8859-1也就是Latin-1编码。这三方一交叉乱码就来了。你想想这个场景开发机是WindowsIDEA里默认文件编码设成了GBK写了个message.welcome欢迎使用系统提交到GitCI服务器是Linux构建时Maven或Gradle按UTF-8读取出来的就是乱码或者反过来你在Linux上用vim写了个UTF-8的properties结果Windows上某个老旧的文本编辑器打开保存一下文件被转成了GBK重启SpringBoot后配置项全变成“????”。这里面最坑的一点是properties文件的编码问题通常不会导致启动报错系统能正常启动只是显示的内容不对。所以排查难度反而比启动异常更大因为没有异常栈、没有报错日志只有运行到某个功能时界面上冒出一串乱码。我在实际项目中还遇到过一种更隐蔽的情况同一个properties文件内部混用了两种编码。比如某个同事在Windows上打开文件追加了几行中文注释用的是GBK而原有内容都是UTF-8。保存后整个文件在有中文注释的位置附近全部变成乱码但其他部分正常。这种“局部乱码”比“全局乱码”更让人头疼因为你会以为是某几行代码写错了。1.2 认清乱码的几种表现形态乱码不是只有一种长相不同表现对应不同根因先学会区分再动手改。根据我的经验常见的乱码形态有下面几类第一类是控制台输出乱码表现为运行SpringBoot项目时System.out.println或日志框架输出的中文变成乱码。这类问题通常和JVM默认字符编码、操作系统终端编码有关不一定和properties文件有直接关系但很多人会把它们混在一起排查。第二类是读取配置项后业务界面上显示乱码比如从Value(${message.welcome})注入的字符串显示为乱码。这种基本可以锁定是properties文件本身在读取时编码不对或者文件保存时就已经是错的。第三类是properties文件在IDE里打开就是乱码。这种情况最直观文件一打开就是“锟斤拷烫烫烫”说明文件实际字节序列和IDE当前使用的解码方式不匹配根源在IDE的文件编码设置或者文件本身的编码已经损坏。第四类是打包部署后乱码本地开发环境一切正常打成jar包丢到服务器上运行就乱码。这类问题往往和构建工具、打包插件读取文件时使用的编码有关比如Maven的project.build.sourceEncoding没有配或者SpringBoot的spring-boot-maven-plugin在打包时用了默认编码。我建议你遇到乱码时先在纸上记录几个信息哪里看到的乱码控制台、日志文件、页面还是IDE里、哪个环境本地还是服务器、什么操作后出现的启动、读取配置、还是显示数据。这几条信息基本能帮你把问题范围缩小一半以上。2. 根因剖析编码链路上到底哪里断了2.1 properties文件背后的编码机制要彻底解决中文乱码得先理解两个底层机制一个是Properties类的加载方式另一个是Java源文件编译期的编码处理。先说Properties类。JDK的java.util.Properties在load(InputStream)方法中明确规定使用ISO-8859-1编码解析字节流同时支持\uXXXX形式的Unicode转义序列。这就意味着如果你直接用Properties.load(new FileInputStream(config.properties))读取一个UTF-8编码且含中文字符的文件读出来的内容一定是乱码。SpringBoot在PropertySource和Environment体系中底层也是基于Properties类的加载逻辑但Spring做了一层封装。org.springframework.core.io.support.PropertiesLoaderSupport和EncodedResource类允许你在加载properties时指定编码。SpringBoot的PropertySource注解从Spring 4.1开始支持encoding属性你可以在注解上直接声明文件编码。再说一个容易被忽略的点Spring Boot 2.4.0之后引入了全新的配置文件加载机制支持spring.config.import、多文档properties、profile分组等新特性。这个新机制在读取application.properties时默认使用UTF-8解析所以如果你用的是新版SpringBoot且配置文件名是application.properties那乱码问题的根因和旧版机制可能完全不同。这里有个很实际的建议动手之前先搞清楚你用的SpringBoot主版本。如果是2.4以前的重点排查Properties.load的ISO-8859-1默认编码链如果是2.4以后的重点排查文件保存时是否真的存成了UTF-8以及IDE是否做了编码转换。方向错了改再多配置都没用。2.2 读取链路中每一环的编码职责一条properties配置从磁盘到Java内存中间经过的每个环节都有自己的编码职责任何一环脱节就会乱码。我习惯把这条链路拆成四段文件保存环节编辑器必须按你期望的编码保存文件。IDEA中File - Settings - Editor - File Encodings里的Default encoding for properties files默认是UTF-8且IDEA默认开启Transparent native-to-ascii conversion。这个选项的作用是在编辑器中显示中文但在保存时自动将非ASCII字符转为\uXXXX转义序列。也就是说文件落地到磁盘时中文其实是以ASCII形式存储的这种文件在任何环境下用Properties.load读取都不会乱码。构建打包环节如果你用的是Mavenpom.xml里必须设置project.build.sourceEncoding为UTF-8否则Maven在复制resources资源文件或编译Java源码时可能使用平台默认编码Windows下就是GBK读取文件导致文件被以错误编码复制到target目录或改变字节序列。运行时加载环节SpringBoot读取application.properties时2.4以下版本需要你手动指定编码方案2.4以上版本默认UTF-8。对于自定义的properties文件可以通过PropertySource(value classpath:xxx.properties, encoding UTF-8)显式指定。JVM输出环节就算文件读取正确如果控制台输出时JVM默认字符集不对依然会显示乱码。启动命令加上-Dfile.encodingUTF-8可以保证System.out和日志输出按UTF-8处理。这四段环节中任何一环出问题最终表现都是中文乱码但修复方式完全不同。这也是为什么网上的解决方案五花八门因为大家遇到的是不同环节的问题。3. 解决方案总览与实操步骤3.1 方案一先确定文件本身的真实编码不管后续怎么改配置第一步永远是确认文件“实际上”是什么编码。注意是实际字节序列不是你在编辑器里看到的显示效果。最可靠的方式是用命令行工具。Linux和macOS下用file命令file -i application.properties输出结果类似application.properties: text/plain; charsetutf-8这就说明文件是UTF-8编码。如果是charsetiso-8859-1或charsetus-ascii那基本可以确定文件保存时编码不对。Windows下可以用PowerShell读取字节流判断$bytes [System.IO.File]::ReadAllBytes(application.properties) $hex ($bytes[0..2] | ForEach-Object { $_.ToString(X2) }) -join Write-Output $hexUTF-8编码的文件如果有BOM前三个字节是EF BB BF如果没有BOM需要看具体内容。这里我的经验是properties文件尽量不要带BOM因为BOM会让第一行配置项的key变得不可读SpringBoot解析时可能会把\uFEFF当成key的一部分导致配置项注入失败。还有一种判断方式用IDEA打开文件看右下角显示的编码是什么。但IDEA显示的是“用当前解码方式读出来的结果”如果文件本身是GBK、IDEA当前也按GBK解码右下角显示GBK这个判断是真实的。如果文件是GBK但IDEA按UTF-8解码右下角会显示UTF-8且内容是乱码这时右下角的显示就不代表文件真实编码了。所以IDEA的判断只能作为参考命令行判断更可靠。3.2 方案二IDEA中配置正确的properties编码大多数乱码问题其实在IDE阶段就能解决。IDEA里有两处关键配置第一处是全局编码设置。路径是File - Settings - Editor - File Encodings把Global Encoding、Project Encoding都设为UTF-8同时把Default encoding for properties files设为UTF-8。这个设置影响IDEA读取和保存properties文件时采用的编码。第二处是Transparent native-to-ascii conversion勾选项。我在很多项目里发现这个选项被一些人误解为“不要勾否则中文会被转成\uXXXX没法看”。这个理解是错的。这个选项的意义在于让你在IDE里看到正常的中文但存储到磁盘时IDEA自动把中文转成\uXXXX形式。因此文件在IDE里可读、在磁盘上对Java解析器友好两边都兼顾。我建议你勾选这个选项并且修改完配置后执行File - Invalidate Caches / Restart清一下缓存。实际操作中有个反直觉的现象IDEA对properties的编码识别有缓存即使你改了全局编码旧文件可能还是按旧编码显示必须重启IDE才生效。如果你手头已经有一个乱码的properties文件可以手动在IDEA右下角切换编码方式先按GBK解码然后Select File Encoding - Convert转成UTF-8后保存。注意这里要选择“Convert”而不是“Reload”Reload只是重新按新编码读取显示不会改变磁盘上的文件编码Convert才会真正把文件字节序列转换。3.3 方案三SpringBoot中显式指定编码如果你不想改IDE、不想转文件编码也可以直接在SpringBoot代码层面解决。最直接的方式是PropertySource注解指定编码Configuration PropertySource(value classpath:custom.properties, encoding UTF-8) public class CustomConfig { Value(${custom.message}) private String message; }这种方式适用于自定义的properties文件。但有个细节需要注意SpringBoot的application.properties和application-{profile}.properties不能通过PropertySource指定编码因为它们是SpringBoot自动加载的走的是ConfigDataEnvironment机制。对于application.properties在SpringBoot 2.4以下版本可以在启动类里自定义PropertySourceLoader来覆盖默认的properties解析逻辑public class Utf8PropertiesLoader implements PropertySourceLoader { Override public String[] getFileExtensions() { return new String[]{properties}; } Override public PropertySource? load(String name, Resource resource) throws IOException { return new OriginTrackedMapPropertySource(name, PropertiesLoaderUtils.loadProperties(new EncodedResource(resource, UTF-8))); } }然后在META-INF/factories里注册org.springframework.boot.env.PropertySourceLoadercom.example.Utf8PropertiesLoader这个方法我试过能用但比较重不推荐一般项目使用。更简单的方式是直接改文件编码统一成UTF-8让SpringBoot的默认UTF-8解析生效。新版本SpringBoot的配置加载默认UTF-8前提是文件本身真的是UTF-8。3.4 方案四用YAML替代properties这可能是最适合新项目的一劳永逸方案。YAML文件.yml从诞生起就基于UTF-8设计SpringBoot对YAML的解析也默认采用UTF-8历史上几乎没见过YAML读取配置导致中文乱码的问题。迁移成本其实不高特别是配置项比较规整的情况下。举个例子原来properties文件app.name用户服务 app.version1.0.0 app.desc这是一个演示服务对应YAMLapp: name: 用户服务 version: 1.0.0 desc: 这是一个演示服务ConfigurationProperties绑定完全不用改Value取值方式也不用改SpringBoot会把YAML的层级结构平铺成app.name这样的key。不过要注意YAML对缩进有严格要求不能用Tab必须用空格。如果一个文件里混用了Tab和空格解析时会报错报错信息往往指向“mapping values are not allowed here”这时候很多人会以为是编码问题实际上是缩进问题。我个人的建议是新项目一律用YAML老项目如果properties文件数量少且结构简单也可以逐步迁移。迁移时注意一个坑YAML里如果值带有特殊字符比如冒号加空格: 、#号需要用单引号或双引号包裹否则会被解析器当成结构符号。3.5 方案五构建期强制统一编码前面说过本地编码没问题不代表打包部署没问题。Maven和Gradle在构建时需要统一文件编码否则会出现“本地好好的、服务器上乱码”的经典场景。Maven项目在pom.xml中加上properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding project.reporting.outputEncodingUTF-8/project.reporting.outputEncoding /propertiesGradle项目在build.gradle中tasks.withType(JavaCompile) { options.encoding UTF-8 }这是一个很多人忽略的配置。我用过一个老项目本地编码全对一执行mvn packagetarget目录下的properties文件里的中文就变成了乱码最后排查发现就是缺少project.build.sourceEncoding导致Maven在复制资源文件时按GBK读取了源文件。这个问题在Windows开发机上尤其高发因为Windows的中文环境默认编码就是GBK。另外SpringBoot的spring-boot-maven-plugin在打包时会把依赖的jar包重新布局。如果某些资源文件已经被错误编码写入打包后自然也是错的。所以构建期统一编码是保障最终产物正确性的关键一环。顺便提一个更隐蔽的场景如果你用了maven-resources-plugin的filtering功能也就是资源文件里带占位符自动替换那么资源插件读取文件的编码也需要显式指定否则同样存在编码错乱风险。4. 常见问题与排查技巧实录4.1 排查乱码问题的定位思路符合我习惯的排查顺序是这样的先用命令行确认文件编码 - 确认IDE编码设置和转换开关 - 本地启动观察是否乱码 - 打包后启动观察是否乱码 - 根据现象锁定环节。我给你一个可以照抄的排查流程表单排查步骤操作判断标准1. 文件真实编码file -i config.properties必须是UTF-82. IDE文件编码IDEA右下角/File Encodings全局和properties均为UTF-83. 转换开关Settings - File EncodingsTransparent native-to-ascii已勾选4. 本地运行mvn spring-boot:run控制台无乱码5. 打包运行java -jar app.jar业务界面无乱码6. JVM编码启动参数加-Dfile.encodingUTF-8输出正常每一步如果发现异常就在那一步停下来修复不要继续往后否则问题现象会叠加更难判断。我在排查过程中还养成了一个习惯乱码问题修复后会把修复前的乱码内容截图或者复制保存一份。因为乱码本身是分析根因的重要线索不同乱码形态对应不同编码错配方式。比如锟斤拷这种经典乱码就是UTF-8字节被当成GBK解码后又转回UTF-8产生的系统这种则是UTF-8字节被Latin-1解码出来的。看到乱码的样子基本就能反推出是哪两步转码出了问题。4.2 典型问题速查表现象根因解决方案IDE里打开就是乱码文件实际编码与IDE解码方式不一致用命令行确认编码在IDE中按正确编码Convert本地启动控制台乱码JVM默认编码非UTF-8或启动脚本未指定-Dfile.encodingUTF-8同时设置JAVA_TOOL_OPTIONS页面显示乱码但配置文件在IDE里正常properties文件保存时被转为\uXXXX以外的形式勾选IDEA的Transparent native-to-ascii conversion打包后乱码本地正常Maven/Gradle构建编码未指定设置project.build.sourceEncodingUTF-8Value注入的值为??首次读取时已被错误编码加载确认文件编码后用PropertySource(encodingUTF-8)指定编码加载部分中文正常部分乱码文件内部混用了多种编码用Python脚本或编辑器统一转码为UTF-84.3 Python批量修复脚本与实战如果你手头有一批已经乱码的properties文件手动一个一个改效率太低。这时的可行方案是写个Python脚本批量转换。我常用的脚本如下兼容Windows和Linuximport os import chardet def convert_to_utf8(file_path): with open(file_path, rb) as f: raw f.read() result chardet.detect(raw) encoding result[encoding] if encoding and encoding.lower() not in (utf-8, ascii): content raw.decode(encoding, errorsignore) with open(file_path, w, encodingutf-8) as f: f.write(content) print(fConverted {file_path}: {encoding} - utf-8) else: print(fSkipped {file_path}: {encoding}) for root, dirs, files in os.walk(src/main/resources): for file in files: if file.endswith(.properties): convert_to_utf8(os.path.join(root, file))chardet库可能不是百分百准确特别是对短文本的判断容易出错。我的建议是先挑一个文件跑一遍确认转换后的内容准确无误再批量执行。批量执行前一定先备份用Git的话确保工作区干净转换后diff一下看看改变是否符合预期。这个脚本也处理不了\uXXXX转义和实际中文混存的问题。如果源文件里已经有一部分是转义形式一部分是真实中文脚本只会把真实中文的部分做编码转换转义部分原样保留。最终文件会是“混合态”但Java解析没问题因为\uXXXX对Properties类来说是天然合法的。4.4 几个我踩过的特殊坑第一SpringBoot的spring.config.encoding配置。不要被网上一些旧博客误导spring.config.encoding在SpringBoot 2.4之后其实已经被移除了。我看到过一些老文章推荐在application.properties里写spring.config.encodingUTF-8这在旧版本确实存在但新版本已经不再支持写了也没用。所以别在这个配置上浪费时间直接把文件编码弄对就行。第二Linux服务器上的locale环境。即便文件编码、打包编码都对了如果部署服务器的JVM默认locale不是UTF-8某些日志输出还是会乱码。启动脚本中建议加上JAVA_OPTS-Dfile.encodingUTF-8 -Dsun.jnu.encodingUTF-8sun.jnu.encoding这个参数影响JVM对文件名的解析在中文文件名场景下也容易出问题。这两个参数一起设置能覆盖绝大多数JVM层面的编码场景。第三PropertySource加载顺序坑。如果你在配置类上用PropertySource加载一个自定义properties同时这个properties又定义了spring.datasource.*之类的参数SpringBoot启动时数据源的自动配置可能比你的PropertySource更早执行导致数据源拿不到配置。这不是编码问题但排查乱码时容易一起碰到所以提醒一下。第四Windows下IDEA与Git的换行符转换。Git在Windows上默认会把换行符从LF转成CRLF这本身不会导致编码变化但如果你的properties文件被某个工具以错误编码读取并重写换行符转换可能会加速“损坏”进程。建议在项目根目录加一个.editorconfig或者.gitattributes统一换行符为LF减少干扰因素。5. 后续扩展思考如果你已经解决了properties的中文乱码问题还可以顺手做两件事来提升配置管理的健壮性。第一把配置文件中的中文提示信息迁移到数据库表或i18n资源文件中。业务提示信息放在properties里其实是比较初级的做法更合理的方式是放到数据库中通过缓存机制加载。这样修改提示内容不用重新打包发布运维也方便。当然对于框架级别的静态配置properties仍然是合适的选择。第二如果你的项目用了Spring Cloud Config或者Nacos作为配置中心配置内容通常以UTF-8存储且支持动态刷新。这种情况下properties本地文件乱码问题被转移到了配置中心侧排查思路不变只是把“本地文件编码”换成“配置中心存储编码”。第三考虑引入ConfigData的新特性将配置分组管理。SpringBoot 2.4之后的spring.config.import支持从多个位置导入配置如果你始终受困于某个properties文件的编码问题可以单独把这个文件改成YAML格式其他保持properties不变。混用格式不会导致问题SpringBoot对每种格式独立解析。我个人的体会是编码问题不像功能bug那样有清晰的报错信息它更像是一种“玄学”。但只要把文件保存、构建打包、运行时加载、输出显示这四个环节理清楚问题就是可控的。每次遇到乱码我都会提醒自己先问一句文件本身的字节到底是什么这个问题的答案往往就已经指向了解决方案。
返回列表