闲着无聊,分析一下多语实现。
一般在使用过程中,我们使用变量的方式来获取数据例如
nc.vo.ml.NCLangRes4VoTransl.getNCLangRes()
.getStrByID("4111002_0", "04111002-0021")
我们是要通过ID的方式获取字符串的值,返回值可以是中文、繁体中文、英文等根据系统选择的语言来返回对应值。
首先返回一个处理类,我们在服务器上使用多语处理和在客户端使用多语处理是不一样的所以又引申出两个NCLangResOnserver和NCLangRes
/**
* 根据是否服务端、客户端返回该端使用的AbstractNCLangRes的具体实例
* @return AbstractNCLangRes
*/
public static synchronized AbstractNCLangRes getNCLangRes(){
if (nclangres == null) {
try {
String sClassname = null;
if (RuntimeEnv.getInstance().isRunningInServer()){
sClassname = "nc.bs.ml.NCLangResOnserver";
}else{
sClassname = "nc.ui.ml.NCLangRes";
}
Method method = Class.forName(sClassname).getMethod("getInstance",null);
nclangres = (AbstractNCLangRes)method.invoke(null,null);
} catch (Exception e) {
e.printStackTrace();
}
}
return nclangres;
}
获取处理类后进行具体的处理方法
/**
根据产品编码,资源ID返回翻译的字符串
* @return String 翻译后的字符串
* @param productCode java.lang.String 产品编码
* @param resId java.lang.String 将要翻译的简体中文字符串的资源ID
*/
public String getStrByID(String productCode, String resId){
String str = getStringByPath(productCode, null,resId, null);
return str;
}
实际上现在用的是适配器的设计模式,进行适配处理,实际上最后处理方法如下
/**
根据产品编码,简体中文,资源ID,搜索路径返回翻译的字符串
* @return String 翻译后的字符串
* @param productCode java.lang.String 产品编码
* @param simpleChinese java.lang.String 将要翻译的简体中文字符串
* @param resId java.lang.String 将要翻译的简体中文字符串的资源ID
* @param path java.lang.String 翻译的资源路径
*/
public String getStringByPath(String productCode, String simpleChinese, String resId, String path){
if(AbstractAppEnvML.enableAppEnvML()){
return getStringByPathInAppEnvML(productCode, simpleChinese, resId,path);
}
String str = null;
try {
ITranslator translator = LanguageTranslatorFactor.getInstance().getTranslator(getCurrLanguage());
str = getString(translator,productCode, simpleChinese, resId, path);
} catch (Exception e) {
e.printStackTrace();
if(simpleChinese != null)
return simpleChinese;
else
return resId;
}
return str;
}
这句根据不同的语言获取不同的翻译器,此处很明显,工厂模式
LanguageTranslatorFactor.getInstance().getTranslator(getCurrLanguage());
实际处理多语的方法我们不难发现是不同翻译器里面的方法
/**
根据具体的翻译器,产品编码,简体中文,资源ID,资源路径返回翻译的字符串
* @return String 翻译后的字符串
* @param translator ITranslator 具体的翻译器
* @param productCode java.lang.String 产品编码
* @param simpleChinese java.lang.String 将要翻译的简体中文字符串
* @param resId java.lang.String 将要翻译的简体中文字符串的资源ID
* @param path java.lang.String 翻译的资源路径
*/
protected String getString(ITranslator translator,String productCode, String simpleChinese, String resId, String path){
String str = null;
try {
if (resId == null){
Exception e = new Exception("resid can't be null");
Logger.error(e.getMessage(), e);
return null;
// productCode = IProductCode.PRODUCTCODE_COMMON;//对于id为null,目前规定都要把资源放在COMMON中
// //对于resID为null的,自动插入到本地文件中为了资源上传到数据库中(这个只是在测试环境中才能用)
// resWrite.writeToFile4Test(StringUtil.filterString(simpleChinese));
}
// //增加一个分类类型来定义用户自定义的资源,分类类型为usercustom
// try{
// str = translator.getString(IProductCode.PRODUCTCODE_USERCUSTOM, simpleChinese, resId);
// }catch(java.util.MissingResourceException e){
//// System.out.println("查找资源失败:"+simpleChinese+"/"+resId+"/"+productCode);
// }
//
if (productCode != null)
try{
str = translator.getString(productCode, simpleChinese, resId);
}catch(java.util.MissingResourceException e){
// System.out.println("查找资源失败:"+simpleChinese+"/"+resId+"/"+productCode);
}
if (str == null&&path != null){
String[] searchPath = splitSearchPath(path);
for (int i = 0; i < searchPath.length; i++){
try{
str = translator.getString(searchPath[i], simpleChinese, resId);
}catch(java.util.MissingResourceException e){
// System.out.println("查找资源失败:"+simpleChinese+"/"+resId+"/"+searchPath[i]);
}
if (str != null)
break;
}
}
if (str == null&&( productCode == null || !productCode.equals(IProductCode.PRODUCTCODE_COMMON))) {
//productCode为COMMON时,就不用再去找了
try{
str = translator.getString(IProductCode.PRODUCTCODE_COMMON, simpleChinese, resId);
}catch(java.util.MissingResourceException e){
// System.out.println("查找资源失败:"+simpleChinese+"/"+resId+"/"+IProductCode.PRODUCTCODE_COMMON);
}
}
if (str == null){
// System.out.println("查找资源失败:"+productCode+"/"+simpleChinese+"/"+resId+"/"+path);
str = simpleChinese;
}
if (str == null && resId != null) {
str = resId;
}
} catch (Exception e) {
e.printStackTrace();
}
return str;
}
我们就简单找一个simplechinese来看一下
/**
* 创建日期:(2004-9-9 16:24:39)
* @return java.lang.String
* @param productCode java.lang.String
* @param simpleChinese java.lang.String
* @param resourceId java.lang.String
*/
public String getString(String productCode, String simpleChinese, String resourceId) {
String str = null;
if(resourceId != null){
MLMessageResource mr = LangResourceBoundleCache.getInstance().getMessageResource(productCode, getLanguage());
if(mr != null){
str = mr.getString(resourceId);
}
}
return str;
}
具体的处理是先获取资源,获取资源的方法如下
public MLMessageResource getMessageResource(String langClass, Language lang) {
langClass = langClass.toLowerCase();
String langCode = lang.getCode();
String resKey = getResKey(langClass, langCode);
MLMessageResource mesResource = (MLMessageResource) getResourceBoundleCache().get(resKey);
if(mesResource == null){
synchronized (LangResourceBoundleCache.class) {
mesResource = (MLMessageResource) getResourceBoundleCache().get(resKey);
if (mesResource == null) {
try {
mesResource = new MLMessageResource(lang,langClass);
getResourceBoundleCache().put(resKey, mesResource);
} catch (Exception e) {
}
}
}
}
return mesResource;
}
其中getResKey这个方法很重要为什么?来看一下
/**
* 根据产品编码和语言编码构造资源缓存用的键值, 同时这个字符串也是资源的路径
*/
private String getResKey(String productCode, String langCode) {
StringBuffer sb = new StringBuffer("lang/");
sb.append(langCode).append("/").append(productCode);
// .append(".")
// .append(productCode).append("res");
return sb.toString();
}
这个是找到对应的目录文件/lang的文件夹下,然后在缓存中(此处为HashMap)获取资源,如果没有就缓存到HashMap中去,供下次同模块的多语调用
回到主线上来,多语资源返回后getString(String resid) 这个字段返回了真正处理后的值,那我们看一下
public String getString(String resid){
String retr = cachMap.get(resid);
if(retr == null){
retr = getStringByType(MLMessageResourceBundle.SPEC_RES_PREFIX_CUSTOMER, resid);
if(retr == null){
retr = getStringByType(MLMessageResourceBundle.SPEC_RES_PREFIX_PARTNER, resid);
}
if(retr == null){
retr = getStringByType(MLMessageResourceBundle.SPEC_RES_PREFIX_HY, resid);
}
if(retr == null){
retr = getStringByType(null, resid);
}
if(retr == null){
retr = getStringByType(MLMessageResourceBundle.SPEC_RES_PREFIX_PLATFORM, resid);
}
if(retr != null){
cachMap.put(resid, retr);
}
}
return retr;
}
这个时候,根据了开发维度做了处理,客户化开发、伙伴开发、行业开发、和平台开发,可以看出,客户化开发最新加载所以维度权重也就不言而喻了。我们主要看一下水平开发维度上的处理
private String getStringByType(String rbType, String resid){
String retr = null;
MLMessageResourceBundle rb = null;
String hyCode = getHYCode();
if(hyCode != null && hyCode.trim().length() > 0){
do {
rb = getMessageResourceBundle(rbType, hyCode);
try{
retr = rb.getString(resid);
}catch(MissingResourceException e){}
if(hyCode.length() > 1){
hyCode = hyCode.substring(0, hyCode.length() - 1);
}else{
break;
}
} while (retr == null);
}
if(retr == null){
rb = getMessageResourceBundle(rbType, "");
try{
retr = rb.getString(resid);
}catch(MissingResourceException e){}
}
return retr;
}
这里面MLMessageResourceBundle起到了作用,我们分析一下
发现了
public class MLMessageResourceBundle extends ResourceBundle implements Serializable
里面有一个构造方法
@SuppressWarnings("unchecked")
public MLMessageResourceBundle(String langClass, Language lang ,String typePrefix) {// 形同lang.englis.1002
langClass = langClass.toLowerCase();
boolean isSysStandard = typePrefix == null || typePrefix.trim().length() == 0;
String langCode = lang.getCode();
String charsetName = lang.getCharsetName();
String resDir = getResKey(langClass, langCode);
UTFProperties properties = new UTFProperties(charsetName);
ArrayList<String> al = getFilesAL(langCode, langClass, resDir);// (ArrayList)getLangResBoundleFiles(langCode).get(resDir);
int count = al == null ? 0 : al.size();
ClassLoader cl = getClass().getClassLoader();
for (int i = 0; i < count; i++) {
String name = al.get(i);
if(!validateFile(name, typePrefix, isSysStandard)){
continue;
}
//
InputStream in = null;
try {
in = cl.getResourceAsStream(name);
if (in != null) {
in = new BufferedInputStream(in);
properties.load(in);
}else{
Logger.error("multi language resource load failed:"+name);
}
} catch (Exception e) {
Logger.error("multi language resource load failed for exception :" + name+" exception msg is "+e.getMessage());
Logger.error(e.getMessage(),e);
} finally {
if (in != null){
try {
in.close();
} catch (Exception e2) {
}
}
}
}
lookup = new HashMap<String, String>(properties);
}
这个时候就已经很明显了
langcode表示语言,langclass表示模块名称,至于resDir就是多余的存放目录文件
getFilesAL(langCode, langClass, resDir);
把对应的properties文件读取出来进行加载,这是在NC6客户端启动时对所有模块做的工作,所以模块太多启动时会比较慢,因为涉及到了太多文件读取。
我们最后发现,支持多语并不是指的多语翻译,是将预置好的编码放到properties文件中,需要的时候读取。这一整套思想还是很值得学习的。
至于为什么要区分server和client,是为解决广域网问题,区分前后台,客户端启用缓存机制。