解決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中這樣的一段處理方法:
- // 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 小貝,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/
解決Spring國際化文案占位符失效問題的方法