解决Spring国际化文案占位符失效问题的方法
写在前面:接下来很长一段时间的文章主要会记录一些项目中实际遇到的问题及对应的解决方案,在相应代码分析时会直指问题所在,不会将无关的流程代码贴出,感兴趣的读者可以自行跟踪。同时希望大家能够将心得体会在评论区分享出来,让大家共同进步!
环境或版本:Spring 3.2.3
现象:利用Spring自带的MessageSource来处理国际化文案,us状态下的文案有部分占位符未被替换,cn状态下的正常。文案如下:
tms.pallet.order.box.qty=The total palletized boxes quantity {0} doesn't match with the received boxes quantity {1},Please double check!
tms.pallet.order.box.qty=打板总箱数件{0},与订单收货总箱数{1}不一致。请检查!
直觉:是不是英文文案太长了,Spring处理时对长度做了限制,仔细想了想Spring应该不会设计的这么坑。
排查:断点跟踪Spring源码(入口:MessageSource的getMessage方法),最后发现了MessageFormat中这样的一段处理方法:
// Indices for segments private static final int SEG_RAW = 0; private static final int SEG_INDEX = 1; private static final int SEG_TYPE = 2; private static final int SEG_MODIFIER = 3; // modifier or subformat /** * Sets the pattern used by this message format. * The method parses the pattern and creates a list of subformats * for the format elements contained in it. * Patterns and their interpretation are specified in the * <a href="#patterns" rel="external nofollow" >class description</a>. * * @param pattern the pattern for this message format * @exception IllegalArgumentException if the pattern is invalid */ @SuppressWarnings("fallthrough") // fallthrough in switch is expected, suppress it public void applyPattern(String pattern) { StringBuilder[] segments = new StringBuilder[4]; // Allocate only segments[SEG_RAW] here. The rest are // allocated on demand. segments[SEG_RAW] = new StringBuilder(); int part = SEG_RAW; int formatNumber = 0; boolean inQuote = false; int braceStack = 0; maxOffset = -1; for (int i = 0; i < pattern.length(); ++i) { char ch = pattern.charAt(i); if (part == SEG_RAW) { if (ch == '\'') { if (i + 1 < pattern.length() && pattern.charAt(i+1) == '\'') { segments[part].append(ch); // handle doubles ++i; } else { inQuote = !inQuote; } } else if (ch == '{' && !inQuote) { part = SEG_INDEX; if (segments[SEG_INDEX] == null) { segments[SEG_INDEX] = new StringBuilder(); } } else { segments[part].append(ch); } } else { if (inQuote) { // just copy quotes in parts segments[part].append(ch); if (ch == '\'') { inQuote = false; } } else { switch (ch) { case ',': if (part < SEG_MODIFIER) { if (segments[++part] == null) { segments[part] = new StringBuilder(); } } else { segments[part].append(ch); } break; case '{': ++braceStack; segments[part].append(ch); break; case '}': if (braceStack == 0) { part = SEG_RAW; makeFormat(i, formatNumber, segments); formatNumber++; // throw away other segments segments[SEG_INDEX] = null; segments[SEG_TYPE] = null; segments[SEG_MODIFIER] = null; } else { --braceStack; segments[part].append(ch); } break; case ' ': // Skip any leading space chars for SEG_TYPE. if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) { segments[part].append(ch); } break; case '\'': inQuote = true; // fall through, so we keep quotes in other parts default: segments[part].append(ch); break; } } } } if (braceStack == 0 && part != 0) { maxOffset = -1; throw new IllegalArgumentException("Unmatched braces in the pattern."); } this.pattern = segments[0].toString(); }
上面的这段代码写的有点让人费解,略微奇特,我们主要看第一个逻辑分支:对每一个待处理的国际化文案模板串中的字符进行遍历,当字符为"'"时,判断后一个字符是否也为“'”,如果是则将“‘”拼接到已处理的StringBuilder中,不是则将inQuote至为True,如果该字符不会‘{'且inQuote为false则将part重新置为0,并且segments[SEG_INDEX]=null的话重新创建StringBuilder对象,否则继续拼接。
原因分析:
- 结合我们配置的英文文案(其中一共有两个占位符,在这这两占位符之前有一个单引号),根据上面Spring的处理源码看,实际处理会是:对该字符串进行逐个字符处理,逐个拼接到已处理的StringBuilder中,当处理到‘{'时,此处part将被置为1,同时segments第1个存储位上会引用StringBuilder类型的对象,程序继续处理下面的待处理的字符,继续拼接(请自行看part!= SEG_RAW的逻辑分支),直到处理到‘}'时,part被重新赋值为0,sefgments的其他位被清空,于是继续处理下面的字符串继续拼接,处理到单引号时,inQuote被置为True,接下来就一路拼接了,不再对后面的“{“做占位符处理。
- 中文文案中两个占位符之间并没有出现单引号,因此解决了问题现象中的第二点,中文文案显示正常。
解决方案:
从源码看只有一种解决方式,{}之间的单引号需要成对出现,我们的处理方式是将文案修改为了:
tms.pallet.order.box.qty=The total palletized boxes quantity {0} doesn''t match with the received boxes quantity {1},Please double check!
直接修改文案其实并不是一种很好的解决方法,最好是能够重写Spring调用applyPattern方法前的某一方法来将单引号替换为双引号。无奈spring 3.2.3版本中对应国际化的处理方法一路private,不给你重写的机会。
查阅相关资料得知,在Spring4.3.2版本中可以通过重写ResourceBundleMessageSource类中的getStringOrNull方法来实现。
长远方案:升级项目中的Spring版本,同时使用更多的新版特性。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
- Python学习笔记(2):数据库访问
- 学习Python语言,这些酷毙的工具你知道几个?
- Mysql备份系列(4)--lvm-snapshot备份mysql数据(全量+增量)操作记录
- 免费体验国内首款写作机器人,就是这么溜!
- Python学习笔记(3):数据集操作-列的统一操作
- Python学习笔记(3):数据集操作-列的统一操作
- Python学习笔记(4):自定义时间类
- Mysql备份系列(3)--innobackupex备份mysql大数据(全量+增量)操作记录
- 微信小程序开发:设置消息推送
- Mysql备份系列(1)--备份方案总结性梳理
- ASP.NET中常用的优化性能的方法(转贴,Icyer收集整理)
- 顶象全场景IoT安全方案解决物联网两大难题
- 诡异的【session丢失】和【<img src="">标签】
- TensorFlow核心使用要点
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- CMAKE学习记录(二)
- maven 中的版本依赖冲突问题
- Manual for Ubuntu Installation
- 修改自定义站点监控页面的样式
- 快速建站“新玩具”—glitch.me
- 踩坑记 | Flutter升级影响了NestedScrollView?
- Android | xml和view的那些事
- Android | 资源冲突覆盖的一些思考
- 如何用脚本自动转化,一个protobuf文件到json格式
- 聊聊dubbo-go的forkingCluster
- 还在用 map[string]interface{} 处理 JSON?告诉你一个更高效的方法——jsonvalue
- 聊聊dubbo-go的failsafeCluster
- 【HDFS】distcp报错Check0sum mismatch
- ffmpeg转换多媒体文件,真香
- 静态库与动态库的那些事