解决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 小贝,Please double check!
tms.pallet.order.box.qty=打板总箱数件{0},与订单收货总箱数小贝不一致。请检查!

直觉:是不是英文文案太长了,Spring处理时对长度做了限制,仔细想了想Spring应该不会设计的这么坑。

排查:断点跟踪Spring源码(入口:MessageSource的getMessage方法),最后发现了MessageFormat中这样的一段处理方法:

  1. // Indices for segments
  2. private static final int SEG_RAW = 0;
  3. private static final int SEG_INDEX = 1;
  4. private static final int SEG_TYPE = 2;
  5. private static final int SEG_MODIFIER = 3; // modifier or subformat
  6. /**
  7. * Sets the pattern used by this message format.
  8. * The method parses the pattern and creates a list of subformats
  9. * for the format elements contained in it.
  10. * Patterns and their interpretation are specified in the
  11. * <a href="#patterns" rel="external nofollow" >class description</a>.
  12. *
  13. * @param pattern the pattern for this message format
  14. * @exception IllegalArgumentException if the pattern is invalid
  15. */
  16. @SuppressWarnings("fallthrough") // fallthrough in switch is expected, suppress it
  17. public void applyPattern(String pattern) {
  18. StringBuilder[] segments = new StringBuilder[4];
  19. // Allocate only segments[SEG_RAW] here. The rest are
  20. // allocated on demand.
  21. segments[SEG_RAW] = new StringBuilder();
  22. int part = SEG_RAW;
  23. int formatNumber = 0;
  24. boolean inQuote = false;
  25. int braceStack = 0;
  26. maxOffset = -1;
  27. for (int i = 0; i < pattern.length(); ++i) {
  28. char ch = pattern.charAt(i);
  29. if (part == SEG_RAW) {
  30. if (ch == ‘\‘‘) {
  31. if (i + 1 < pattern.length()
  32. && pattern.charAt(i+1) == ‘\‘‘) {
  33. segments[part].append(ch); // handle doubles
  34. ++i;
  35. } else {
  36. inQuote = !inQuote;
  37. }
  38. } else if (ch == ‘{‘ && !inQuote) {
  39. part = SEG_INDEX;
  40. if (segments[SEG_INDEX] == null) {
  41. segments[SEG_INDEX] = new StringBuilder();
  42. }
  43. } else {
  44. segments[part].append(ch);
  45. }
  46. } else {
  47. if (inQuote) { // just copy quotes in parts
  48. segments[part].append(ch);
  49. if (ch == ‘\‘‘) {
  50. inQuote = false;
  51. }
  52. } else {
  53. switch (ch) {
  54. case ‘,‘:
  55. if (part < SEG_MODIFIER) {
  56. if (segments[++part] == null) {
  57. segments[part] = new StringBuilder();
  58. }
  59. } else {
  60. segments[part].append(ch);
  61. }
  62. break;
  63. case ‘{‘:
  64. ++braceStack;
  65. segments[part].append(ch);
  66. break;
  67. case ‘}‘:
  68. if (braceStack == 0) {
  69. part = SEG_RAW;
  70. makeFormat(i, formatNumber, segments);
  71. formatNumber++;
  72. // throw away other segments
  73. segments[SEG_INDEX] = null;
  74. segments[SEG_TYPE] = null;
  75. segments[SEG_MODIFIER] = null;
  76. } else {
  77. --braceStack;
  78. segments[part].append(ch);
  79. }
  80. break;
  81. case ‘ ‘:
  82. // Skip any leading space chars for SEG_TYPE.
  83. if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) {
  84. segments[part].append(ch);
  85. }
  86. break;
  87. case ‘\‘‘:
  88. inQuote = true;
  89. // fall through, so we keep quotes in other parts
  90. default:
  91. segments[part].append(ch);
  92. break;
  93. }
  94. }
  95. }
  96. }
  97. if (braceStack == 0 && part != 0) {
  98. maxOffset = -1;
  99. throw new IllegalArgumentException("Unmatched braces in the pattern.");
  100. }
  101. this.pattern = segments[0].toString();
  102. }

复制代码

上面的这段代码写的有点让人费解,略微奇特,我们主要看第一个逻辑分支:对每一个待处理的国际化文案模板串中的字符进行遍历,当字符为"‘"时,判断后一个字符是否也为“‘”,如果是则将“‘”拼接到已处理的StringBuilder中,不是则将inQuote至为True,如果该字符不会‘{‘且inQuote为false则将part重新置为0,并且segments[SEG_INDEX]=null的话重新创建StringBuilder对象,否则继续拼接。

原因分析:

  1. 结合我们配置的英文文案(其中一共有两个占位符,在这这两占位符之前有一个单引号),根据上面Spring的处理源码看,实际处理会是:对该字符串进行逐个字符处理,逐个拼接到已处理的StringBuilder中,当处理到‘{‘时,此处part将被置为1,同时segments第1个存储位上会引用StringBuilder类型的对象,程序继续处理下面的待处理的字符,继续拼接(请自行看part!= SEG_RAW的逻辑分支),直到处理到‘}‘时,part被重新赋值为0,sefgments的其他位被清空,于是继续处理下面的字符串继续拼接,处理到单引号时,inQuote被置为True,接下来就一路拼接了,不再对后面的“{“做占位符处理。
  2. 中文文案中两个占位符之间并没有出现单引号,因此解决了问题现象中的第二点,中文文案显示正常。

解决方案:

从源码看只有一种解决方式,{}之间的单引号需要成对出现,我们的处理方式是将文案修改为了:

tms.pallet.order.box.qty=The total palletized boxes quantity {0} doesn‘‘t match with the received boxes quantity 小贝,Please double check!

直接修改文案其实并不是一种很好的解决方法,最好是能够重写Spring调用applyPattern方法前的某一方法来将单引号替换为双引号。无奈spring 3.2.3版本中对应国际化的处理方法一路private,不给你重写的机会。

查阅相关资料得知,在Spring4.3.2版本中可以通过重写ResourceBundleMessageSource类中的getStringOrNull方法来实现。

长远方案:升级项目中的Spring版本,同时使用更多的新版特性。

http://www.ljhseo.com/
http://www.xyrjkf.net/
http://www.xyrjkf.cn/
http://www.xyrjkf.com.cn/
http://www.zjdygsi.cn/
http://www.zjdaiyun.cn/
http://www.jsdygsi.cn/
http://www.xyrjkf.top/
http://www.xyrjkf.com/
http://www.daiyunzj.cn/
http://ljhseo.com/
http://xyrjkf.net/
http://xyrjkf.cn/
http://xyrjkf.com.cn/
http://zjdygsi.cn/
http://zjdaiyun.cn/
http://jsdygsi.cn/
http://xyrjkf.top/
http://xyrjkf.com/
http://daiyunzj.cn/

原文地址:https://www.cnblogs.com/ljhseocom/p/8999524.html

时间: 2024-10-12 16:36:01

解决Spring国际化文案占位符失效问题的方法的相关文章

SPRING多个占位符配置文件解析源码研究--转

原文地址:http://www.cnphp6.com/archives/85639 Spring配置文件: <context:property-placeholder location="classpath:/settings.properties" /> <context:property-placeholder location="classpath:/conf.properties"/> settings.properties redi

解决关于配置文件的占位符无法读取问题

前提是: 其他的配置文件中已经配置了<context:property-placeholder location="classpath:jdbc.properties"/> 主要原因是: Spring容器采用反射扫描的发现机制,在探测到Spring容器中有一个org.springframework.beans.factory.config.PropertyPlaceholderConfigurer的Bean就会停止对剩余PropertyPlaceholderConfigur

深入Spring Boot:那些注入不了的Spring占位符(${}表达式)

Spring里的占位符 spring里的占位符通常表现的形式是: <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="url" value="${jdbc.url}"/> </bean> 或者 @Confi

深入Spring Boot:那些注入不了的 Spring 占位符 ( ${} 表达式 )

Spring里的占位符 spring里的占位符通常表现的形式是: 1 2 3 <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="url" value="${jdbc.url}"/> </bean> 或者

boost在lambda表达式中调用占位符参数的成员函数的方法

boost中提供了lambda表达式的用法,但是lambda表达式的功能还不是很强大,在其中只能对lambda的占位符参数_1等使用最基本的操作符,如+-*/,可是很多时候如果传入的占位符参数是一个对象指针的话,我们可能想要调用这个类的成员函数. 我在开发中遇到了这个问题,需要在stl的算法中传入一个函数来调用对象的比较函数,因为感觉这样太麻烦,还需要重新定义一个函数,所以想起了lambda表达式,c++11的lambda表达式我倒是没试过,可是受项目开发环境所限,只能选择boost.但是我用的

Hibernate4.1之后关于占位符的问题

hibernate 4.1之后对于HQL中查询参数的占位符做了改进,如果仍然用老式的占位符会有类似如下的告警信息 [main] WARN [org.hibernate.hql.internal.ast.HqlSqlWalker] – [DEPRECATION] Encountered positional parameter near line 1, column 95. Positional parameter are considered deprecated; use named para

js字符串使用占位符拼接小结

js字符串使用占位符拼接,由于项目中经常用到jquery拼接字符串,各种引号很disgusting所以写了一个占位符拼接的的方法 String.prototype.signMix= function() { if(arguments.length === 0) return this; var param = arguments[0], str= this; if(typeof(param) === 'object') { for(var key in param str = str.repla

spring加载配置文件无法解析占位符问题:Could not resolve placeholder &#39;from&#39; in string value &quot;${from}&quot;

Could not resolve placeholder 'from' in string value "${from}" 解决: 在spring的xml配置文件中当有多个*.properties文件需要加载时, 应当集中在一个xml文件中加载,建议在主xml文件中加载,即(applicationContext.xml)中加载, 这样就不需要关注 子xml文件 与 *.properties 加载顺序问题 加载多个*.properties文件,以','隔开 <context:pr

8 -- 深入使用Spring -- 1...4 属性占位符配置器

8.1.4 属性占位符配置器 PropertyPlaceholderConfigurer 是一个容器后处理器,负责读取Properties属性文件里的属性值,并将这些属性值设置成Spring配置文件的数据. 通过使用PropertyPlaceholderConfigurer后处理器,可以将Spring配置文件中的部分数据放在属性文件中设置. XML : <?xml version="1.0" encoding="UTF-8"?> <!-- Spri