消息处理与国际化
Struts2 国际化是建立在 Java 国际化基础上的:
为不同国家/语言提供对应的消息资源文件
Struts2 框架会根据请求中包含的
Locale 加载对应的资源文件
通过程序代码取得该资源文件中
指定 key 对应的消息
为不同国家/语言提供对应的消息资源文件
Struts2 框架会根据请求中包含的
Locale 加载对应的资源文件
通过程序代码取得该资源文件中
指定 key 对应的消息
书写:i18n_en_US.properties或i18n_zh_CN.properties
配置国际化资源文件
Action 范围资源文件:在Action类文件所在的路径建立名为ActionName_language_country.properties 的文件
包范围资源文件:在包的根路径下建立文件名为package_language_country.properties 的属性文件,一旦建立,处于该包下的所有 Action 都可以访问该资源文件。注意:包范围资源文件的 baseName 就是package,不是Action所在的包名。
全局资源文件
命名方式: basename_language_country.properties
struts.xml<constant name="struts.custom.i18n.resources" value="baseName"/>
struts.propertiesstruts.custom.i18n.resources=baseName
临时指定资源文件:<s:i18n.../> 标签的 name 属性指定临时的国际化资源文件
包范围资源文件:在包的根路径下建立文件名为package_language_country.properties 的属性文件,一旦建立,处于该包下的所有 Action 都可以访问该资源文件。注意:包范围资源文件的 baseName 就是package,不是Action所在的包名。
全局资源文件
命名方式: basename_language_country.properties
struts.xml<constant name="struts.custom.i18n.resources" value="baseName"/>
struts.propertiesstruts.custom.i18n.resources=baseName
临时指定资源文件:<s:i18n.../> 标签的 name 属性指定临时的国际化资源文件
加载资源文件的顺序
假设我们在某个 ChildAction 中调用了getText("username"):
(1)加载和 ChildAction 的类文件在同一个包下的系列资源文件 ChildAction.properties
(2)加载 ChildAction 实现的接口 IChild,且和 IChildn 在同一个包下 IChild.properties 系列资源文件。
(3)加载 ChildAction 父类 Parent,且和 Parent 在同一个包下的 baseName 为 Parent.properties 系列资源文件。
(4) 若 ChildAction 实现 ModelDriven 接口,则对于getModel()方法返回的model 对象,重新执行第(1)步操作。
(5) 查找当前包下 package.properties 系列资源文件。
(6) 沿着当前包上溯,直到最顶层包来查找 package.properties 的系列资源文件。
(7) 查找 struts.custom.i18n.resources 常量指定 baseName 的系列资源文件。
(8) 直接输出该key的字符串值。
(1)加载和 ChildAction 的类文件在同一个包下的系列资源文件 ChildAction.properties
(2)加载 ChildAction 实现的接口 IChild,且和 IChildn 在同一个包下 IChild.properties 系列资源文件。
(3)加载 ChildAction 父类 Parent,且和 Parent 在同一个包下的 baseName 为 Parent.properties 系列资源文件。
(4) 若 ChildAction 实现 ModelDriven 接口,则对于getModel()方法返回的model 对象,重新执行第(1)步操作。
(5) 查找当前包下 package.properties 系列资源文件。
(6) 沿着当前包上溯,直到最顶层包来查找 package.properties 的系列资源文件。
(7) 查找 struts.custom.i18n.resources 常量指定 baseName 的系列资源文件。
(8) 直接输出该key的字符串值。
SP 页面访问国际化消息:
不带占位符:
<s:text name="key"/>
表单元素的 label 属性:可替换为 key 或使用 getText() 方法,并对其进行强制 OGNL 解析
带占位符:
在 <s:text.../> 标签中使用多个 <s:param.../> 标签来填充消息中的占位符。
Struts2 直接在国际化消息资源文件中通过 “${}” 使用表达式,该表达式将从值栈中获取对应的属性值
不带占位符:
<s:text name="key"/>
表单元素的 label 属性:可替换为 key 或使用 getText() 方法,并对其进行强制 OGNL 解析
带占位符:
在 <s:text.../> 标签中使用多个 <s:param.../> 标签来填充消息中的占位符。
Struts2 直接在国际化消息资源文件中通过 “${}” 使用表达式,该表达式将从值栈中获取对应的属性值

访问国际化消息
Action 访问国际化消息:
若 Action 类继承了 ActionSupport ,则可调用 TextProvider 接口的 getText 方法。
若 Action 类继承了 ActionSupport ,则可调用 TextProvider 接口的 getText 方法。
利用超链接实现动态加载国际化资源文件
Struts2 使用 i18n 拦截器处理国际化,并且将其注册在默认的拦截器中
i18n拦截器在执行Action方法前,自动查找请求中一个名为request_locale 的参数。如果该参数存在,拦截器就将其作为参数,转换成Locale对象,并将其设为用户默认的Locale(代表国家/语言环境)。并把其设置为 session 的 WW_TRANS_I18N_LOCALE 属性
若 request 没有名为request_locale 的参数,则 i18n 拦截器会从 Session 中获取 WW_TRANS_I18N_LOCALE 的属性值,若该值不为空,则将该属性值设置为浏览者的默认Locale
若 session 中的 WW_TRANS_I18N_LOCALE 的属性值为空,则从 ActionContext 中获取 Locale 对象。
i18n拦截器在执行Action方法前,自动查找请求中一个名为request_locale 的参数。如果该参数存在,拦截器就将其作为参数,转换成Locale对象,并将其设为用户默认的Locale(代表国家/语言环境)。并把其设置为 session 的 WW_TRANS_I18N_LOCALE 属性
若 request 没有名为request_locale 的参数,则 i18n 拦截器会从 Session 中获取 WW_TRANS_I18N_LOCALE 的属性值,若该值不为空,则将该属性值设置为浏览者的默认Locale
若 session 中的 WW_TRANS_I18N_LOCALE 的属性值为空,则从 ActionContext 中获取 Locale 对象。
访问国际化消息 源码解析
1 public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable { 2 3 private transient TextProvider textProvider ; 4 private Container container; 5 6 public Locale getLocale() { 7 ActionContext ctx = ActionContext. getContext(); 8 if (ctx != null ) { 9 return ctx.getLocale(); 10 } else { 11 if (LOG .isDebugEnabled()) { 12 LOG.debug("Action context not initialized" ); 13 } 14 return null ; 15 } 16 } 17 //当调用这个方法时会调用TextProviderSupport的getText方法 18 public String getText(String aTextName) { 19 return getTextProvider().getText(aTextName); 20 } 21 } 22 23 public class TextProviderSupport implements ResourceBundleTextProvider { 24 25 private Class clazz; 26 private LocaleProvider localeProvider; 27 private ResourceBundle bundle; 28 29 public String getText(String key) { 30 return getText(key, key, Collections.emptyList()); 31 } 32 33 public String getText(String key, String defaultValue, List<?> args) { 34 Object[] argsArray = ((args != null && !args.equals(Collections.emptyList())) ? args.toArray() : null); 35 if (clazz != null) { 36 //在这里进行读取.加载资源文件的顺序 37 return LocalizedTextUtil.findText( clazz, key, getLocale(), defaultValue, argsArray); 38 } else { 39 return LocalizedTextUtil.findText( bundle, key, getLocale(), defaultValue, argsArray); 40 } 41 } 42 } 43 在LocalizedTextUtil中调用的方法加载资源文件 44 public static String findText (Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args, 45 ValueStack valueStack) { 46 String indexedTextName = null; 47 if (aTextName == null) { 48 if (LOG .isWarnEnabled()) { 49 LOG.warn("Trying to find text with null key!" ); 50 } 51 aTextName = ""; 52 } 53 (1)加载和 ChildAction 的类文件在同一个包下的系列资源文件 ChildAction.properties 54 (2)加载 ChildAction 实现的接口 IChild,且和 IChildn 在同一个包下 IChild.properties 系列资源文件。 55 (3)加载 ChildAction 父类 Parent,且和 Parent 在同一个包下的 baseName 为 Parent.properties 系列资源文件 56 if (aTextName.contains("[" )) { 57 int i = -1; 58 59 indexedTextName = aTextName; 60 61 while ((i = indexedTextName.indexOf("[" , i + 1)) != -1) { 62 int j = indexedTextName.indexOf("]" , i); 63 String a = indexedTextName.substring(0, i); 64 String b = indexedTextName.substring(j); 65 indexedTextName = a + "[*" + b; 66 } 67 } 68 69 // search up class hierarchy 70 String msg = findMessage(aClass, aTextName, indexedTextName, locale, args, null , valueStack); 71 72 if (msg != null ) { 73 return msg; 74 } 75 若 ChildAction 实现 ModelDriven 接口,则对于getModel()方法返回的model 对象,重新执行第(1)步操作。 76 if (ModelDriven.class.isAssignableFrom(aClass)) { 77 ActionContext context = ActionContext.getContext(); 78 79 ActionInvocation actionInvocation = context.getActionInvocation(); 80 81 if (actionInvocation != null) { 82 Object action = actionInvocation.getAction(); 83 if (action instanceof ModelDriven) { 84 Object model = ((ModelDriven) action).getModel(); 85 if (model != null) { 86 msg = findMessage(model.getClass(), aTextName, indexedTextName, locale, args, null, valueStack); 87 if (msg != null ) { 88 return msg; 89 } 90 } 91 } 92 } 93 } 94 95 96 for (Class clazz = aClass; 97 (clazz != null) && !clazz.equals(Object.class ); 98 clazz = clazz.getSuperclass()) { 99 100 String basePackageName = clazz.getName(); 101 while (basePackageName.lastIndexOf('.' ) != -1) { 102 basePackageName = basePackageName.substring(0, basePackageName.lastIndexOf('.' )); 103 String packageName = basePackageName + ".package"; 104 msg = getMessage(packageName, locale, aTextName, valueStack, args); 105 106 if (msg != null ) { 107 return msg; 108 } 109 110 if (indexedTextName != null) { 111 msg = getMessage(packageName, locale, indexedTextName, valueStack, args); 112 113 if (msg != null ) { 114 return msg; 115 } 116 } 117 } 118 } 119 int idx = aTextName.indexOf("." ); 120 121 if (idx != -1) { 122 String newKey = null; 123 String prop = null; 124 125 if (aTextName.startsWith(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX )) { 126 idx = aTextName.indexOf( ".", XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX .length()); 127 128 if (idx != -1) { 129 prop = aTextName.substring(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX .length(), idx); 130 newKey = XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX + aTextName.substring(idx + 1); 131 } 132 } else { 133 prop = aTextName.substring(0, idx); 134 newKey = aTextName.substring(idx + 1); 135 } 136 137 if (prop != null) { 138 Object obj = valueStack.findValue(prop); 139 try { 140 Object actionObj = ReflectionProviderFactory.getInstance().getRealTarget(prop, valueStack.getContext(), valueStack.getRoot()); 141 if (actionObj != null) { 142 PropertyDescriptor propertyDescriptor = ReflectionProviderFactory.getInstance().getPropertyDescriptor(actionObj.getClass(), prop); 143 144 if (propertyDescriptor != null) { 145 Class clazz = propertyDescriptor.getPropertyType(); 146 147 if (clazz != null) { 148 if (obj != null ) 149 valueStack.push(obj); 150 msg = findText(clazz, newKey, locale, null , args); 151 if (obj != null ) 152 valueStack.pop(); 153 154 if (msg != null ) { 155 return msg; 156 } 157 } 158 } 159 } 160 } catch (Exception e) { 161 LOG.debug("unable to find property " + prop, e); 162 } 163 } 164 } 165 166 查找 struts.custom.i18n.resources 常量指定 baseName 的系列资源文件。 167 GetDefaultMessageReturnArg result = null; 168 if (indexedTextName == null) { 169 result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage); 170 } else { 171 result = getDefaultMessage(aTextName, locale, valueStack, args, null ); 172 if (result != null && result.message != null) { 173 return result.message ; 174 } 175 result = getDefaultMessage(indexedTextName, locale, valueStack, args, defaultMessage); 176 } 177 178 // could we find the text, if not log a warn 179 if (unableToFindTextForKey(result) && LOG.isDebugEnabled()) { 180 String warn = "Unable to find text for key '" + aTextName + "' " ; 181 if (indexedTextName != null) { 182 warn += " or indexed key '" + indexedTextName + "' " ; 183 } 184 warn += "in class '" + aClass.getName() + "' and locale '" + locale + "'"; 185 LOG.debug(warn); 186 } 187 188 return result != null ? result.message : null; 189 }