記解決Spring國際化文案佔位符失效問題

寫在前面:接下來很長一段時間的文章主要會記錄一些項目中實際遇到的問題及對應的解決方案,在相應代碼分析時會直指問題所在,不會將無關的流程代碼貼出,感興趣的讀者能夠自行跟蹤。同時但願你們可以將心得體會在評論區分享出來,讓你們共同進步!java

環境或版本:Spring 3.2.3spring

現象:利用Spring自帶的MessageSource來處理國際化文案,us狀態下的文案有部分佔位符未被替換,cn狀態下的正常。文案以下:app

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應該不會設計的這麼坑。ui

排查:斷點跟蹤Spring源碼(入口:MessageSource的getMessage方法),最後發現了MessageFormat中這樣的一段處理方法:this

// 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">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對象,不然繼續拼接。spa

緣由分析:設計

  • 結合咱們配置的英文文案(其中一共有兩個佔位符,在這這兩佔位符以前有一個單引號),根據上面Spring的處理源碼看,實際處理會是:對該字符串進行逐個字符處理,逐個拼接到已處理的StringBuilder中,當處理到‘{’時,此處part將被置爲1,同時segments第1個存儲位上會引用StringBuilder類型的對象,程序繼續處理下面的待處理的字符,繼續拼接(請自行看part!= SEG_RAW的邏輯分支),直處處理到‘}’時,part被從新賦值爲0,sefgments的其餘位被清空,因而繼續處理下面的字符串繼續拼接,處理到單引號時,inQuote被置爲True,接下來就一路拼接了,再也不對後面的「{「作佔位符處理。
  • 中文文案中兩個佔位符之間並無出現單引號,所以解決了問題現象中的第二點,中文文案顯示正常。

解決方案:rest

從源碼看只有一種解決方式,{}之間的單引號須要成對出現,咱們的處理方式是將文案修改成了:code

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,不給你重寫的機會。orm

查閱相關資料得知,在Spring4.3.2版本中能夠經過重寫ResourceBundleMessageSource類中的getStringOrNull方法來實現。

長遠方案:升級項目中的Spring版本,同時使用更多的新版特性。

相關文章
相關標籤/搜索