ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Malformed \uxxxx encoding 异常排查与修复

Malformed \uxxxx encoding 异常排查与修复 IllegalArgumentException: Malformed \uxxxx encoding.这行报错我在过去几年里至少碰上过七八次。第一次遇到时是一套 Spring 老项目的启动脚本日志滚了几屏最后一行就这一句没有行号、没有文件名只有一句冷冰冰的 Malformed \uxxxx encoding.。当时我的第一反应是字符编码问题——UTF-8 还是 GBK 没对上折腾了半小时才发现方向完全错了这跟文件编码一点关系都没有是java.util.Properties在解析\u转义序列时发现后面跟的不是四位合法十六进制数字直接掀桌子了。后来陆续在 Dubbo、Kafka 客户端配置、国际化资源包、Maven 过滤资源这些地方又撞见它才慢慢把触发条件和修法摸清楚。这篇就把我踩过的坑、定位手段和几种修法一次讲透不管你手上是 Spring Boot、老式 Java Web 还是大数据组件只要配置走的是.properties这套思路都能直接用。1. 先搞清楚这个异常到底在报什么很多人第一次看到这个报错会下意识往文件编码上想然后去改file.encoding、加 BOM、转 GBK折腾一圈毫无进展。原因在于报错里的 encoding 指的是Unicode 转义序列的编码格式不是文件本身的字符集编码。java.util.Properties的规范里明确规定\u后面必须紧跟恰好四位十六进制数字大小写均可用来表示一个 Unicode 字符。只要这四位里有任何一位不是0-9、a-f、A-F解析器立刻抛异常不给任何回旋余地。1.1 从 JDK 源码看\u的校验逻辑如果你翻一下 JDK 的java.util.Properties会看到load0方法里调了一个loadConvert核心逻辑大致是这样// 摘自 JDK 中 java.util.Properties#loadConvert 的等价逻辑 if (aChar \\) { aChar in[off]; if (aChar u) { // Read the xxxx int value 0; for (int i 0; i 4; i) { aChar in[off]; switch (aChar) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: value (value 4) aChar - 0; break; case a: case b: case c: case d: case e: case f: value (value 4) 10 aChar - a; break; case A: case B: case C: case D: case E: case F: value (value 4) 10 aChar - A; break; default: throw new IllegalArgumentException(Malformed \\uxxxx encoding.); } } out[outLen] (char) value; } else { // 处理 \t \n \r \f 等其他转义 } }这段代码想表达的事情非常直白解析器逐字符扫描一旦遇到反斜杠就把下一个字符读进来判断。如果是小写字母u那么无条件进入四位十六进制读取流程读满四位才算完。读的过程中碰到任何非十六进制字符或者读到字符串结尾就抛异常。这里有一个很多人忽略的细节只有小写u会触发这个分支。\U、\u之外的其他字母走的是另一条路径。这意味着C:\Users\test这种大写 U 开头的路径不会报这个错虽然它也会被悄悄改写后面会讲而D:\user\data这种小写 u 开头的就会当场炸掉。这个区别是定位问题的关键前提先记住它。1.2 为什么偏偏是\u不是其他转义Properties支持的转义序列其实有一整张表我在下面整理了一份你可以对照着看转义序列解析结果是否触发严格校验\\一个字面反斜杠否\t制表符否\n换行符否\r回车符否\f换页符否\uXXXX对应码点的字符是四位必须合法\反斜杠空格一个空格否\、\:、\#、\!对应符号本身否\xx 为其他任意字符直接得到x反斜杠被丢弃否看最后一行这就是问题的根源所在。Properties对未知转义的处理方式是静默丢弃反斜杠不报错。所以D:\data\test会被解析成D:datatest——反斜杠全没了路径静默错掉程序跑起来才发现文件找不到比直接报错难查十倍。而\u是唯一一个会进入严格模式的转义所以它成了所有反斜杠问题里唯一会大声喊出来的那个。换句话说你看到的这个异常其实是在帮你。那些没报错的路径配置很可能已经悄悄错掉了。1.3 影响范围哪些场景会被它打中只要配置读取链路上出现了java.util.Properties.load()就都在射程之内。我按遇到频率排了个序场景典型载体触发概率Windows 本地路径application.properties、自定义配置极高正则表达式 / 脚本片段校验配置、日志脱敏规则高国际化资源包messages_zh_CN.properties中高构建期资源过滤注入Maven resource filtering、Gradle processResources中中间件客户端配置Kafka、Dubbo、ZooKeeper、Hadoop中加密密钥 / 连接串数据源配置、第三方对接配置低但难排查这里面要单独说明一下 Spring Boot 的情况。Spring Boot 2.x 读取application.properties用的是自研的OriginTrackedPropertiesLoader不是直接调java.util.Properties但它的 Unicode 转义校验逻辑和 JDK 是一致的报错文案可能略有不同比如提示 Invalid unicode escape 并带上位置信息。反过来如果你用的是老的 Spring 3/4、PropertiesLoaderUtils、ResourceBundle、PropertySource加载自定义 properties 文件那走的就完全是 JDK 那套load0报错就是标准的 Malformed \uxxxx encoding.。所以看到不同文案别慌本质是同一个问题。2. 五类最常见的触发场景逐一拆解定位问题之前先得知道问题长什么样。我把这些年实际碰到过的触发场景归纳成五类你可以拿自己的配置文件对照着扫一遍大概率能直接命中。2.1 Windows 路径里的单反斜杠这是绝对的重灾区。开发在 Windows 上写配置顺手就写了本机路径# 会报 Malformed \uxxxx encoding. upload.dirD:\user\data # 这个不报错但会被静默改写成 D:uploadsfile upload.dirD:\uploads\file # 大写 U 不触发 \u 分支不报错但同样会被吃掉反斜杠 backup.dirC:\Users\admin\backup第一条报错的原因是\user里的\u后面跟的是ser\s不是十六进制字符当场抛异常。第二条不报错是因为\u后面跟的是ploap同样不是十六进制——等等这里应该也报错才对。确实D:\uploads\file里\u后面是ploadp非法一样会报错。真正不报错的是第三种情况C:\Users\admin\backup因为\U是大写不走\u分支走的是未知转义路径反斜杠被静默丢弃最终读到的值是C:Usersadminbackup。这种才是最阴险的——程序不报错只是某天突然告诉你文件不存在。还有一个极端情况值得单独提如果\u后面恰好跟了四位合法十六进制字符比如D:\uabcd\x解析器会把它当成一个 Unicode 码点 UABCD 直接转换路径变成一段完全对不上的乱码同样不报错。所以不要以为不报错就没事。2.2 正则表达式和脚本片段正则里到处是反斜杠写进.properties时如果没做二次转义基本必炸# 想表达 \u 开头的正则结果反斜杠被 Properties 吃掉 pattern\\u[0-9a-fA-F]{4} # 下面这种写法直接报错因为 \u 后面是 [0-9[ 非法 pattern\u[0-9a-fA-F]{4}第一条\\u是正确的——两个反斜杠在 Properties 里解析为一个字面反斜杠后面紧跟的u就是普通字符正则拿到的是\u符合预期。第二条\u[0-9a-fA-F]{4}因为[不是十六进制字符直接抛异常。同样的坑还出现在日志脱敏规则、SQL 模板片段、shell 命令片段、JSON 字符串内嵌等场景。判断标准很简单只要这个反斜杠是给下一层解析器用的就必须在 Properties 层面双写。2.3 转义不完整的 Unicode 字符这类问题的来源通常是从别的工具转过来的。比如用某个脚本把中文转成 Unicode 转义时位数不够、或者手动拼写时少打了一位# 少一位报错 greeting\u4f60 # 含非法字符报错 greeting\u4f6G # JS / 现代语言风格的花括号写法报错 emoji\u{1F600} # 八位大写 U 写法不报错但也不生效 emoji\U0001F600这里有个有意思的现象\U0001F600这种八位写法在 Java 的Properties里不会报错因为\U是大写走的是未知转义分支结果是反斜杠被丢弃读到的值是U0001F600这么一串字面文本。很多从 Python 或者 JavaScript 转过来的配置文件都会踩这个坑因为那些语言里\U是支持的Java 的 Properties 不支持。2.4 行尾单反斜杠引发的续行错位Properties有一个续行机制如果一行以单个反斜杠结尾解析器会把下一行接上来并且吃掉下一行的前导空白。这个机制本身是设计好的但当路径以反斜杠结尾时会出大问题# 假设原始意图是配一个以反斜杠结尾的目录 base.dirD:\data\ # 下一行是另一个配置 timeout30实际读到的结果会变成base.dirD:\datatimeout30这样一条完全错乱的键值。因为行尾的\触发了续行D:\data\结尾的反斜杠既是路径的一部分又是续行符解析器只把它当续行符处理然后直接把下一行拼过来了。更麻烦的是这种错位往往在几行之后才引发\u报错让人误以为是别的位置出了问题。所以排查时不要只盯报错的那一行往前多看两行。2.5 构建期资源过滤注入的脏数据这一类最隐蔽。你本地写的配置文件完全合法但打出来的包里就报错了。典型是 Maven 的资源过滤resources resource directorysrc/main/resources/directory filteringtrue/filtering /resource /resources配置文件里写了upload.dir${upload.base}而upload.base这个属性在pom.xml或settings.xml里被定义成了 Windows 路径D:\user\upload。过滤之后生成到target/classes里的文件就变成了upload.dirD:\user\upload运行时直接报错。这种问题的排查要点是不要只看源码目录里的文件一定要去看构建产物里的那份。target/classes/application.properties或者build/resources/main/下的那一份才是真正被加载的文件。3. 三分钟定位到具体那一行这个异常最让人恼火的地方是没有行号。JDK 抛异常的时机在逐字符解析过程中堆栈里只有Properties.load0、Properties.load这几层调用完全看不出是哪个文件的哪一行。所以定位手段得自己造。3.1 从堆栈倒推调用链先看堆栈它的价值在于告诉你是哪个文件被加载时炸的。堆栈大致长这样java.lang.IllegalArgumentException: Malformed \uxxxx encoding. at java.util.Properties.loadConvert(Properties.java:611) at java.util.Properties.load0(Properties.java:468) at java.util.Properties.load(Properties.java:396) at com.example.config.ConfigLoader.load(ConfigLoader.java:42) at com.example.Application.main(Application.java:18)关键信息在第 4 行往后。找到你自己的代码调用点就能知道是哪个配置文件的加载触发的。如果中间夹着 Spring 的PropertiesLoaderUtils.fillProperties那就是PropertySource注解指定的那个文件。但如果堆栈里全是框架代码最后一层也是框架的通用加载器那就得靠扫描脚本了。3.2 手写一个扫描脚本一次扫全部这个脚本我用了好几年Python 版逻辑是逐字符扫描正确处理\\转义只报告真正会触发异常的\u# scan_properties.py # 用法: python scan_properties.py 目录或文件路径 import os import sys HEX set(0123456789abcdefABCDEF) def scan_line(line): 返回该行所有非法的 \u 位置元素为 (列号, 实际读到的四位片段) problems [] i 0 n len(line) while i n: ch line[i] if ch ! \\: i 1 continue # 行尾孤立反斜杠属于续行符不校验 if i 1 n: break nxt line[i 1] if nxt u: seg line[i 2:i 6] if len(seg) 4 and all(c in HEX for c in seg): i 6 continue problems.append((i, seg)) break else: # 其他转义整体跳过两个字符这样 \\ 会被自然吃掉 i 2 return problems def scan_file(path): hits [] with open(path, r, encodingutf-8, errorsreplace) as f: for lineno, raw in enumerate(f, 1): line raw.rstrip(\r\n) stripped line.lstrip() # 行首注释行不参与解析直接跳过 if stripped.startswith(#) or stripped.startswith(!): continue for col, seg in scan_line(line): hits.append((lineno, col, seg, line)) return hits def main(): target sys.argv[1] files [] if os.path.isfile(target): files.append(target) else: for root, _, names in os.walk(target): for name in names: if name.endswith(.properties): files.append(os.path.join(root, name)) total 0 for path in files: hits scan_file(path) if hits: print( * 70) print(文件:, path) for lineno, col, seg, line in hits: total 1 print( 第 %d 行, 第 %d 列: \\u 后面的内容为 %r % (lineno, col, seg)) print( 原文: %s % line) print( * 70) print(共发现 %d 处可疑位置扫描了 %d 个文件 % (total, len(files))) if __name__ __main__: main()用起来很简单# 扫描整个源码目录 python scan_properties.py src/main/resources # 扫描构建产物排查过滤注入问题 python scan_properties.py target/classes # 扫描单个文件 python scan_properties.py ./application.properties脚本有两个设计上的小心思值得说一下。第一它把行首的#和!注释行跳过了——Properties的注释行不会进入转义解析写在注释里的\u完全安全所以不该报出来否则会淹没真正的问题。第二遇到非u的转义时它一次跳过两个字符这样\\u0041这种正确写法不会被误报而\u0041这种合法转义也会被正确放行。如果你不想装 Python给一个 Java 版的等价实现塞进项目里跑个 main 方法就行import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.util.*; public class PropertiesEscapeScanner { private static final String HEX 0123456789abcdefABCDEF; public static void main(String[] args) throws IOException { Path root Paths.get(args.length 0 ? args[0] : src/main/resources); ListPath files new ArrayList(); Files.walk(root) .filter(p - p.toString().endsWith(.properties)) .forEach(files::add); int total 0; for (Path p : files) { ListString lines Files.readAllLines(p, StandardCharsets.UTF_8); for (int i 0; i lines.size(); i) { String line lines.get(i); String trimmed line.trim(); if (trimmed.startsWith(#) || trimmed.startsWith(!)) { continue; } int idx findBadUnicode(line); if (idx 0) { total; System.out.println(p 第 (i 1) 行 第 idx 列); System.out.println( line); } } } System.out.println(共发现 total 处可疑位置); } private static int findBadUnicode(String line) { int i 0; int n line.length(); while (i n) { char c line.charAt(i); if (c ! \\) { i; continue; } if (i 1 n) { break; } char next line.charAt(i 1); if (next u) { if (i 6 n) { return i; } for (int k i 2; k i 6; k) { if (HEX.indexOf(line.charAt(k)) 0) { return i; } } i 6; } else { i 2; } } return -1; } }3.3 二分剔除快速收敛到病灶有时候配置是从别处拷来的一个几千行的大文件脚本一下子报出十几处但只有一处是真正在加载时被触发的。这时候用二分法最快先把文件切成两半注释掉后半部分在每行前面加#启动看还报不报。报就在前半部分不报就在后半部分。如此递归一般三四轮就能锁定到具体行。# 快速把文件第 500 行之后全部注释掉 sed -i 500,$s/^/#/ application.properties # 恢复用 sed -i s/^#// application.properties这个办法看着土但实测比什么工具都快因为Properties的解析是顺序的第一个非法位置就会抛异常二分法天然适配。4. 从根上修五种解决路径与选型建议定位到了之后修复本身并不复杂但选哪种修法要看具体场景。我下面按改动成本从低到高排一遍你可以直接跳到符合自己情况的那个。4.1 双写反斜杠最直接的修法如果这个值确实需要包含字面反斜杠标准做法就是双写# 修改前会报错 upload.dirD:\user\data # 修改后 upload.dirD:\\user\\data # 正则同理 pattern\\u[0-9a-fA-F]{4}原理很简单\\在Properties里被解析成一个字面反斜杠后面的字符就不会再被当成转义起始符看了。注意是每一个反斜杠都要双写漏掉一个照样炸。注意双写只对Properties层面生效。上层代码拿到的值就是单个反斜杠不要再多写一层否则会变成两个反斜杠传下去。4.2 改用正斜杠最省事的修法如果这个值的用途是文件路径而且上层代码用的是java.io.File、java.nio.file.Path、Spring 的Resource那么直接换成正斜杠是最省事的upload.dirD:/user/dataJava 的 IO 和 NIO 在 Windows 上对正斜杠是完全兼容的new File(D:/user/data)工作正常。我个人的经验是新项目里所有路径配置一律用正斜杠从源头避免这类问题因为团队里总有人会在某个时刻往配置里粘一个 Windows 路径。唯一的例外是需要把路径拼给外部命令行工具比如调用某些原生程序时那些工具可能只认反斜杠。这种情况下还是老老实实双写。4.3 用\u005C绕过可读性最差的修法\u005C是反斜杠的 Unicode 码点。如果你只想改一处又不想双写可以这么写upload.dirD:\u005Cuser\u005Cdata解析器读到\u005C会转成反斜杠后续的字符继续按普通字符处理。这个写法的好处是位置精确——只改需要改的地方不像双写那样容易改花眼。坏处是几乎没人能一眼看懂维护性很差。我只在对第三方提供的、不能改结构的配置文件做最小化补丁时用过这个办法。4.4 换配置格式或换解析器如果这个配置文件本身就是要放大量路径、正则、脚本片段长期来看Properties的转义规则就是个负担。可以考虑换格式目标格式优势迁移成本备注YAML无需转义反斜杠可读性好中Spring Boot 原生支持JSON结构清晰工具链成熟中只支持双引号字符串反斜杠需双写TOML语法干净路径友好中高生态相对小XML Properties支持loadFromXML中有 XML 自己的转义规则要处理换 YAML 是最顺的路径因为 Spring Boot 对application.yml的支持是一等公民Properties里的keyvalue直接映射成 YAML 的缩进结构即可upload: dir: D:\user\data backup: C:\Users\admin\backupYAML 里反斜杠在不加引号的标量中不触发转义所以路径可以直接写。但如果加了双引号YAML 自己也有转义规则又会踩坑所以写 YAML 时路径尽量不加引号或者用单引号。另一个方向是不换格式换解析器。Apache Commons Configuration2 的PropertiesConfiguration有自己的一套转义处理行为跟 JDK 的不完全一致某些场景下更宽松。但要注意它的默认分隔符和Properties有差异迁移前一定要做回归测试。4.5 换成loadFromXML老代码的兼容解法JDK 从 1.5 开始提供了Properties.loadFromXML()对应的文件格式是标准的 XML?xml version1.0 encodingUTF-8? !DOCTYPE properties SYSTEM http://java.sun.com/dtd/properties.dtd properties comment上传目录配置/comment entry keyupload.dirD:\user\data/entry entry keytimeout30/entry /properties这种格式下不会再走\u校验反斜杠原样保留。代价是文件变啰嗦了而且 XML 有自己的转义规则、要写成实体只是比\u好处理一些。另外 DTD 那行在某些严格环境下会因为网络请求被卡住改成不写 DOCTYPE 也可以loadFromXML实际上不强制校验 DTD。我一般只在对老系统做最小改动止血时用它——其他代码都不动只把文件格式一换加载方式从load(stream)改成loadFromXML(stream)一行代码搞定。4.6 五种方案横向对比方案适用场景改动范围可读性推荐度双写\\值里必须有字面反斜杠只改配置中高换正斜杠/文件路径类配置只改配置高最高\u005C第三方文件的最小补丁只改一处低低换 YAML/JSON新建项目、大型配置配置代码高高新项目loadFromXML老系统止血配置一行代码中中我的实际选择顺序是路径类先换正斜杠正则类双写新项目直接上 YAML老项目改动受限时用 XML 格式止血。这四句话覆盖了我这几年遇到的所有情况。5. 常见问题速查表与避坑清单修复方案讲完了但实际操作里真正耗时间的从来不是怎么改而是为什么改了还是报错、为什么没报错的也出问题了。这一节把我攒下来的排查经验和误判集锦整理出来。5.1 症状到原因速查表症状最可能的原因优先排查项启动即报 Malformed \uxxxx配置里有小写\u开头的非法转义用扫描脚本全量扫一遍本地正常打包后报错构建期资源过滤注入了脏数据检查target/classes下的产物只是警告程序能跑\u后跟了四位合法十六进制被转换成了别的字符对比读到的实际值与预期值不报错但路径找不到文件未知转义被静默丢弃反斜杠没了打印读到的实际字符串报错行号对不上前一行以单反斜杠结尾触发了续行往上多查两行中文乱码同时报转义错两个独立问题别混在一起查先解决转义再处理字符集5.2 三个最容易误判的方向第一个误判是往文件编码上查。我一开始也这么想。但事实是Properties.load(InputStream)会按 ISO-8859-1 解码字节流而Properties.load(Reader)用你指定的字符集。这个差异影响的是中文等非 ASCII 字符能否正确读出来跟\u转义的合法性校验没有任何关系。改file.encoding、加 BOM、转 GBK一个都解决不了这个问题。第二个误判是怀疑 Spring 版本或者 JDK 版本。这个异常从 JDK 1.2 的Properties实现到今天的 JDK 21逻辑几乎没变过升级或降级版本都不会让它消失。第三个误判是以为注释里写了\u也会报错。实际上行首以#或!开头的整行会被LineReader跳过根本不进入转义解析。所以注释里随便写不用双写。但要注意行中间的#不是注释keyvalue # 说明里# 说明是值的一部分会一起被解析这里面的\u照样炸。5.3 我踩过的几个具体坑坑一用 IDE 的 Properties 编辑器自动转义。IntelliJ IDEA 有个 Transparent native-to-ascii conversion 选项勾上之后你在编辑器里看到的是中文但文件里存的是\u4F60\u597D这种转义。这个功能本身很好用但当它和手动输入的\u混在一起时很容易产生冲突。我就遇到过一次手动写了\u0041想表示字母 A结果 IDE 在处理时又做了一次转换变成了\\u0041读出来的值变成了字面的\u0041字符串排查了半天。建议团队统一约定这个选项开还是关别一半人开一半人关。坑二跨平台配置被反复修改。团队里有 Windows 和 Mac 两边开发时upload.dir这个配置经常被两边的人互相覆盖。Windows 同事改成D:\user\upload提交Mac 同事改回/Users/xxx/upload提交来回几次之后.properties里开始出现各种混合写法。我的解决办法是在配置里只写相对路径或者用环境变量占位具体值通过${UPLOAD_DIR}由运行环境注入彻底避开分隔符问题。坑三native2ascii工具的遗留产物。JDK 9 之前有个native2ascii命令专门用来把中文转成\uXXXX形式很多老项目的资源包都是它生成的。如果后期有人手工编辑过这些文件很容易破坏转义格式。排查这类文件时一定要用脚本全量扫不要凭肉眼一行行看——几千行的资源文件靠眼睛是看不过来的。坑四续行符和路径尾部反斜杠的双重混淆。有一次同事配了个备份目录值写成了backup.dirE:\archive\结尾一个反斜杠。文件里这一行下面紧跟着retention.days30。结果读出来的值是E:\archiveretention.days30程序把一个完整的键值对当成了路径的一部分。这个问题不报错只是备份目录检查一直失败查了两个小时才想到是续行符。结论配置值永远不要以单个反斜杠结尾。5.4 一个顺手的自检小工具我现在每个项目都会在test目录下放一个校验测试用前面那个PropertiesEscapeScanner的逻辑扫描所有.properties文件一旦发现非法\u就让构建失败import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.file.*; import java.util.*; import static org.junit.jupiter.api.Assertions.assertTrue; public class PropertiesEscapeTest { Test public void allPropertiesFilesShouldBeEscapedProperly() throws IOException { Path root Paths.get(src/main/resources); ListString bad new ArrayList(); Files.walk(root) .filter(p - p.toString().endsWith(.properties)) .forEach(p - { try { ListString lines Files.readAllLines(p); for (int i 0; i lines.size(); i) { String line lines.get(i).trim(); if (line.startsWith(#) || line.startsWith(!)) { continue; } if (findBadUnicode(lines.get(i)) 0) { bad.add(p 第 (i 1) 行); } } } catch (IOException e) { bad.add(p 读取失败); } }); assertTrue(bad.isEmpty(), 发现非法的 \\u 转义: bad); } private int findBadUnicode(String line) { String hex 0123456789abcdefABCDEF; int i 0, n line.length(); while (i n) { char c line.charAt(i); if (c ! \\) { i; continue; } if (i 1 n) break; if (line.charAt(i 1) u) { if (i 6 n) return i; for (int k i 2; k i 6; k) { if (hex.indexOf(line.charAt(k)) 0) return i; } i 6; } else { i 2; } } return -1; } }这个测试跑一次不到一秒但能在提交阶段就拦住问题比等到线上启动失败再回来查强太多。加了它之后我们团队再没出现过打包后才暴露的转义问题。最后分享一个小习惯我在写任何.properties配置文件时只要值里出现了反斜杠就会条件反射式地检查三件事——是不是路径换正斜杠、是不是给下层用的双写、是不是结尾绝不能以单反斜杠结尾。这个三连问养成肌肉记忆之后这类问题基本就从我的工作里消失了。至于那个完全绕不过去的场景——比如对接的第三方工具硬性要求给一个 Windows 反斜杠路径——我现在会统一放到一处Configuration类里用 Java 代码生成字符串而不是写死在 properties 文件里让转义这件事在编译期就解决掉而不是留到运行期去猜。
返回列表