一般在Java项目中,文本的国际化可以通过property文件来实现。对于静态文本来说(比如按钮名称、表格列名等),property文件非常合适;但对于动态文本来说(比如业务表的状态字段),property文件的形式就有些局限了。
这里简单解释下动态文本和静态文本的差异。
动态文本是在运行期间,根据客户的实际情况,可能会有变化的内容。比如现在有一个业务字段,叫“差异原因”,现在有2种原因。随着业务的开展,将来可能有3,4,5种甚至更多。也就是说是会动态增长的。
而静态文本是不会变的。比如某个按钮,业务再怎么变,这个按钮还是在那里,除非对按钮有可见的限制,但那已经不是文本国际化的问题,而是可见权限的管理问题。
之前我一直有种误解,就是认为动态文本还分为两种,区分的标准就是,程序中是否会使用其code值做业务判断。比如业务状态字段,程序中可能会根据对状态值的判断,走不同的流程分支。因此,程序中会使用它,应该由程序员来维护和管理。另一种就是程序根本不关心该字段的具体内容,字段内容只是用作简单的显示使用。
但是后来我发现,这种区分标准的界限很模糊。你很难确定说,这个字段的值将来程序是否会使用。因为分类不同,其实现方式就不同。如果从一种分类变为另一种分类,就需要大量的修改代码。
现在我认为,其本质都是一种都是动态文本。程序员关心的始终都是动态文本的key,所有key对应的value,都应该交由java国际化框架来解决。
其实property文件也只是Java国际化文本的一种存储介质而已,你完全可以采用数据库、xml、Java类等等其他的持久化方案。或者,你完全可以自己写一套,就像以前很多老系统中,存在的字典表这种简化的国际化方案(基本只需支持中文,并且会有很严重的性能问题,因为你需要频繁的将字典表与业务表进行关联)。
property文件方案对于解决动态文本国际化的局限性主要有:
上面的第一点和第三点,就完全否决了由非java程序员进行维护的的可能。
因此,我们需要修改方案,使运维人员可以方便的维护动态文本。property数据库持久方案就可以解决以上问题。
关于采用数据库实现java国际化,网上也有很多现成的例子,我主要参考的是这篇文章:http://java.dzone.com/articles/resource-bundle-tricks-and
以下是我对ResourceBundle的Control的实现
monospace; border: none; background-color: transparent; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;">public class ResourceBundleDBControl extends Control {
@Override
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException {
return new MyResources(baseName,locale);
}
protected class MyResources extends ListResourceBundle {
@SuppressWarnings("unused")
private Locale locale;
private String baseName;
public MyResources(String baseName,Locale locale) {
this.baseName = baseName;
this.locale = locale;
}
@Override
protected Object[][] getContents() {
DetachedCriteria dc = DetachedCriteria.forClass(Code.class);
dc.createAlias("codeType", "type");
dc.setProjection(Projections.projectionList()
.add(Projections.property("key"))
.add(Projections.property("value")));
dc.add(Restrictions.eq("type.code", baseName));
ApplicationContext ctx = ApplicationContextProvider.getContext();
IQueryService queryService = (IQueryService) ctx.getBean("queryService");
@SuppressWarnings("unchecked")
Listresult = (List) queryService.query(dc); Object [][] r = new Object[result.size()][2]; for(int i = 0;i < result.size(); i++){ r[i][0] = result.get(i)[0]; r[i][1] = result.get(i)[1]; } return r; } }}
ResourceBundle的control机制可以参考其API文档。默认ResourceBundle可以从class和property文件加载内容。通过客户化control的实现,我们可以从数据库加载ResourceBundle。
对于新的ResourceBundle的使用可以参考以下代码(即将customerized control作为参数传入即可):
public String getTextFromDB(String bundleName,String key){
ResourceBundle b = ResourceBundle.getBundle(bundleName, new ResourceBundleDBControl());
return b.getString(key);
}
注意:ResourceBundle本身有缓存机制。比如ListResourceBundle本身有一个map作为内部的缓存。因此,修改动态文本后,需要重启应用服务器。或者修改ListResourceBundle的实现,不应用缓存。
下图中的lookup即为内部缓存:?