对于涉及到多语言用户使用的应用,国际化是一条必经之路。Tomcat也不例外,做为一款成熟且成功的开源软件,其用户量巨大,受到全球各国的工程师喜爱。
我们在阅读其源码时,一定见到过类似下面这样的内容:
throw new IllegalArgumentException(sm.getString("coyoteConnector.parseBodyMethodNoTrace"));
其中这句sm.getString("coyoteConnector.parseBodyMethodNoTrace")
里的sm,就是实现国际化的核心StringManager。 在许多类中,我们都会发现这样的字段声明:
/** * The string manager for this package. */ protected static final StringManager sm = StringManager.getManager(Connector.class);
此时,会得到一个当前Locale对应的StringManager。注意这里保证每个包只对应一个manager。单例的实现可以参考前面的文章:
public static final synchronized StringManager getManager( String packageName, Locale locale){ Map<Locale,StringManager> map = managers.get(packageName); if (map == null) { map = new LinkedHashMap<Locale,StringManager>(LOCALE_CACHE_SIZE, 1, true); managers.put(packageName, map); } StringManager mgr = map.get(locale); if (mgr == null) { mgr = new StringManager(packageName, locale); map.put(locale, mgr); } return mgr; }
资源文件的载入是使用ResourceBundle实现的,文件是对应的packageName+LocalStrings
private StringManager(String packageName, Locale locale) { String bundleName = packageName + ".LocalStrings"; ResourceBundle bnd = null; try { bnd = ResourceBundle.getBundle(bundleName, locale); } catch (MissingResourceException ex) { } bundle = bnd; if (bundle != null) { Locale bundleLocale = bundle.getLocale(); if (bundleLocale.equals(Locale.ROOT)) { this.locale = Locale.ENGLISH; } else { this.locale = bundleLocale; } } else { this.locale = null; } }
这块有两个可以关注的点:
ResourceBundle
MessageFormat
其中ResourceBundle
会根据指定的Locale加载对应的资源文件信息,我们一般资源文件都是xxx_fr.properties/xxx_es.properties这种形式,bundle的locale会自动匹配。
而MessageFormat
则会自动替换信息中的占位符,例如资源文件中有类似这样的内容:
Could not contact {0}:{1}. Tomcat may not be running.
其中{0}:{1}就是占位符,在实际使用时,会被真实的值替换。而背后的实现,正是MessageFormat.使用时,就直接传入实际值即可,如下:
log.error(sm.getString("catalina.stopServer.connectException", s.getAddress(), String.valueOf(s.getPort())));`
在需要信息提示时,调用方法getString
来获取,注意catch块中的注释,比较有意思。
public String getString(String key) { if (key == null){ String msg = "key may not have a null value"; throw new IllegalArgumentException(msg); } String str = null; try { // Avoid NPE if bundle is null and treat it like an MRE if (bundle != null) { str = bundle.getString(key); } } catch (MissingResourceException mre) { //bad: shouldn't mask an exception the following way: // str = "[cannot find message associated with key '" + key + // "' due to " + mre + "]"; // because it hides the fact that the String was missing // from the calling code. //good: could just throw the exception (or wrap it in another) // but that would probably cause much havoc on existing // code. //better: consistent with container pattern to // simply return null. Calling code can then do // a null check. str = null; } return str; }
以上,即为Tomcat国际化的实现方式,对于我们的应用开发,可以用来参考。