Tomcat的国际化实现

对于涉及到多语言用户使用的应用,国际化是一条必经之路。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会自动匹配。

Tomcat的国际化实现

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国际化的实现方式,对于我们的应用开发,可以用来参考。


上一篇:java的DateFormat、SimpleDateFormate类源码的详解


下一篇:时间插件