2006-03-25

『某個專案』的小秘密 - 為什麼property file不用再native2ascii,只要用UTF-8編碼就可以?

這是我在『某個專案』裡的疑惑:

Google大師說:property file一定要native2ascii,除非你只用ISO字元集,不然一定會出現一團麵粉!

但是在該『某個專案』裡:卻不用那麼麻煩!用UTF-8編碼就可以了!

不知所措但懶惰成性的我,自然選擇的簡單的方式,但是當我在不是該『某個專案』的環境裡這麼搞時,那一團麵粉又出現了!天啊!

經過我不屈不撓的明察暗訪之後,終於發現該『某個專案』的小秘密!

外面的世界是這樣的:
JDK的Properties用ISO-8859-1的方式將property file讀進來,所以乖乖用native2ascii的property file就正常的搞定了!

該『某個專案』的世界是這樣的:
偷懶的UTF-8 property file也被Struts用ISO-8859-1的方式讀進去了,但是一切的事情就發生在org.apache.struts.util.PropertyMessageResources這支程式上面,本來這支程式應該乖乖地呆在struts.jar裡,但是不知被哪個狠心的人給做掉了,卻出現在\src\Web\org\apache\struts\util\ PropertyMessageResources.java,然後再裡面出現一段中文的注解:
while (names.hasMoreElements()) {
String key = (String) names.nextElement();
String value = props.getProperty(key);
try {
// 強制預設所有的 resource file 編碼一定要為 ISO8859_1 或 UTF8,若為 UTF8 在此會正確的解碼
value = new String(value.getBytes("ISO8859_1"), "UTF8");
}
catch (UnsupportedEncodingException e) {
}

messages.put(messageKey(localeKey, key), value);

if (log.isTraceEnabled()) {
log.trace(" Saving message key '"
+ messageKey(localeKey, key));
}
}
就是它被從錯誤的ISO-8859-1轉成正確的UTF-8,從此公主與王子就過著幸福快樂的生活了!

還沒完...

第一,我在struts-config.xml裡的發現他還有兩個有用的attribute:className與factory,也就是從字面上來猜,應該可以不用去把struts.jar動手腳,可以從這裡定義自己的MessageSource就可以了,這是我猜的啦,我沒試過!

第二,若是我想裝認真,用native2ascii,但是property file裡會出現"堃"(方方土),那property file就一定得用UTF-8編碼存檔,若直接用native2ascii,也會出現一團麵粉,原因是native2ascii預設是採用JVM的編碼,所以會爛掉,但是native2ascii有一個encoding的參數,只要用native2ascii -encoding UTF-8 xxx.properties就行了!

最後自從發現這個偷吃步以後,我再也沒用過native2ascii 了,即使後來拋棄Struts投入Spring MVC陣營後,也是這樣搞的!只是Spring也沒有提供很好的『插入』機制,所以還是改的有點醜,只要就是要改寫 org.springframework.context.support.ResourceBundleMessageSource這一隻。
第一步,不一樣的地方只有class="xxx"這一段:
<!-- 因為不想用native2ascii,所以用UTF-8編碼,但是JDK一定用ISO讀檔,所以要將ISO還原成UTF-8 -->
<bean id="messageSource" class="com.ntc.i18n.UnicodeResourceBundleMessageSource">
<property name="basenames">
<list>
<value>ntc</value>
<value>validator</value>
</list>
</property>
</bean>

第二步,改寫org.springframework.context.support.ResourceBundleMessageSource:
package com.ntc.i18n;
package com.xxx.i18n;
public class UnicodeResourceBundleMessageSource extends ResourceBundleMessageSource {
...
protected String resolveCodeWithoutArguments(String code, Locale locale) {
String result = null;
for (int i = 0; result == null && i < this.basenames.length; i++) { ResourceBundle bundle = getResourceBundle(this.basenames[i], locale); if (bundle != null) { result = getStringOrNull(bundle, code);
}
}
return result;
}
...
protected MessageFormat getMessageFormat(ResourceBundle bundle,
String code, Locale locale) throws MissingResourceException {

synchronized (this.cachedBundleMessageFormats) {
Map codeMap = (Map) this.cachedBundleMessageFormats.get(bundle);
Map localeMap = null;
if (codeMap != null) {
localeMap = (Map) codeMap.get(code);
if (localeMap != null) {
MessageFormat result = (MessageFormat) localeMap
.get(locale);
if (result != null) {
return result;
}
}
}

String msg = getStringOrNull(bundle, code);
if (msg != null) {
if (codeMap == null) {
codeMap = new HashMap();
this.cachedBundleMessageFormats.put(bundle, codeMap);
}
if (localeMap == null) {
localeMap = new HashMap();
codeMap.put(code, localeMap);
}
MessageFormat result = createMessageFormat(msg, locale);
localeMap.put(locale, result);
return result;
}

return null;
}
}

/**
* 因為不想用native2ascii,所以用UTF-8編碼,但是JDK一定用ISO讀檔,所以要將ISO還原成UTF-8
*
* @param bundle
* @param key
* @return
*/
private String getStringOrNull(ResourceBundle bundle, String key) {
try {
String value = bundle.getString(key);
if (StringUtils.isNotBlank(value)) {
return new String(value.getBytes("iso-8859-1"), "utf-8");
}
}
catch (MissingResourceException ex) {
// assume key not found
// -> do NOT throw the exception to allow for checking parent
// message source
return null;
}
catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
return null;
}
}

沒有留言:

張貼留言