Spring国际化
在web开发中经常会遇到国际化的问题,那么在spring mvc中如何实现动态国际化。Spring使用ResourceBundleMessageSource实现国际化资源的定义。使用LocaleResolver实现本地化信息的解析,使用LocaleChangeInterceptor实现本地化信息的监听(来实现url参数动态指定locale)。
I18N
人们常把I18N作为“国际化”的简称,其来源是英文单词 internationalization的首末字符i和n。18为中间的字符数。
A、LocaleResolver(本地化解析器)
org.springframework.web.servlet.LocaleResolver
public interface LocaleResolver
DispatcherServlet允许使用客户端本地化信息自动解析消息。这个工作由实现LocaleResolver的对象来完成。
但收到请求时,DispatcherServlet查找LocaleResolver,若找到就是用它来设置Locale信息。
A.1、LocaleResolver的实现类
A.1.1、AcceptHeaderLocaleResolver
org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
public class AcceptHeaderLocaleResolver extends Object implements LocaleResolver
这个本地化解析器检查请求中客户端浏览器发送的accept-language头信息,这里包含客户端操作系统的本地化信息。
A.1.2、CookieLocaleResolver
org.springframework.web.servlet.i18n.CookieLocaleResolver
java.lang.Object org.springframework.web.util.CookieGenerator org.springframework.web.servlet.i18n.CookieLocaleResolver
public class CookieLocaleResolver extends CookieGenerator implements LocaleResolver
这个本地化解析器检查客户端中的cookie是否包含本地化信息。若有就使用。
A.1.3、SessionLocaleResolver
org.springframework.web.servlet.i18n.SessionLocaleResolver
java.lang.Object org.springframework.web.servlet.i18n.AbstractLocaleResolver org.springframework.web.servlet.i18n.SessionLocaleResolver
public class SessionLocaleResolver extends AbstractLocaleResolver
这个本地化解析器检查客户端中的session是否包含本地化信息。若有就使用。
A.1.4、FixedLocaleResolver
org.springframework.web.servlet.i18n.FixedLocaleResolver
java.lang.Object org.springframework.web.servlet.i18n.AbstractLocaleResolver org.springframework.web.servlet.i18n.FixedLocaleResolver
public class FixedLocaleResolver extends AbstractLocaleResolver
这个本地化解析器返回一个固定的本地化信息。默认值为当前JVM的locale。
A.2、如何获得客户端的locale
通过RequestContext.getLocale()方法来获取由本地化解析器解析的客户端的本地化信息。
A.2.1、RequestContext
org.springframework.web.servlet.support.RequestContext
public class RequestContext extends Object
Context holder for request-specific state, like current web application context, current locale, current theme, and potential binding errors. Provides easy access to localized messages and Errors instances.
Request特殊状态的上下文持有者,如:当前web的application context,当前locale,当前主题和可能捆绑的错误。使访问本地化的信息和错误实例。
A.2.2、RequestContext取得locale的例子
public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse res) throws Exception { RequestContext requestContext = new RequestContext(req); Locale myLocale = requestContext.getLocale(); System.out.println(myLocale); }
B、MessageSource
org.springframework.context.MessageSource
public interface MessageSource
spring通过实现MessageSource接口,来支持国际化。MessageSource来定义国际化需要资源的接口。MessageSource有很多实现方法,ResourceBundleMessageSource是一个常用的实现。它按照ResourceBundle标准实施。
B.1、ResourceBundleMessageSource
org.springframework.context.support.ResourceBundleMessageSource
public class ResourceBundleMessageSource extends AbstractMessageSource implements BeanClassLoaderAware
org.springframework.context.support.MessageSourceSupport
org.springframework.context.support.AbstractMessageSource
org.springframework.context.support.ResourceBundleMessageSource
MessageSource implementation that accesses resource bundles using specified basenames. This class relies on the underlying JDK's ResourceBundle implementation, in combination with the JDK's standard message parsing provided by MessageFormat.
它是MessageSource的一个实现,它访问资源束,资源束的路径由的basenames属性来指定。这个类依赖于JDK的底层类java.util.ResourceBundle。并结合由java.text.MessageFormat类提供的JDK标准message解析。
set方法
void setBasename(String basename)
Set a single basename, following ResourceBundle conventions: essentially, a fully-qualified classpath location. If it doesn't contain a package qualifier (such as org.mypackage), it will be resolved from the classpath root.
Messages will normally be held in the "/lib" or "/classes" directory of a web application's WAR structure. They can also be held in jar files on the class path.
Note that ResourceBundle names are effectively classpath locations: As a consequence, the JDK's standard ResourceBundle treats dots as package separators. This means that "test.theme" is effectively equivalent to "test/theme", just like it is for programmatic java.util.ResourceBundle usage.
设置单个basename,遵照ResourceBundle协定:一个合格的classpath路径。若它没有包含包路径(如:org.mypackage),它将被解析classpath的根目录。
信息一般被放在WAR结构的web项目的/lib或/classes目录下。你也可以放在对应的jar文件中。
要注意的是:JDK中使用“.”作为路径的分隔符。也就是说“test.theme”实际表示为根目录下的“test/theme”目录。
B.1.1、ResourceBundleMessageSource的配置
<!-- 资源文件绑定器 -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="messages" /> <property name="useCodeAsDefaultMessage" value="true" /> </bean>
其中,message-info是你的properties文件的通用名。如:我的配置文件叫messages.properties,messages_zh_CN.properties等等。
B.1.2、范例说明
范例1
1.配置messagesSource的bean
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!--bean的名称必须定义为messageSource --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename"> <value>spring.chapter13.demo2.messages</value> <!-- 前面的是包名,messages是配置文件的前缀 --> </property> </bean> </beans>
关于basename的命名方式曾困扰我好久,特别是要加上包的名称,指定properties存放位置。一定要注意不然会报org.springframework.context.NoSuchMessageException的错
2.资源文件messages_zh_CN.properties
customer.name=david, age \: {0}, URL \: {1}
ABC=sdsd
3.测试类
package spring.chapter13.demo2; import java.util.Locale; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; public class test { public static void main(String[] args) { String fileName = "src/spring/chapter13/demo2/bean.xml"; ApplicationContext context = new FileSystemXmlApplicationContext(fileName); String name = ""; name = context.getMessage("ABC", null, Locale.CHINA); System.out.println(name); String namechinese = context.getMessage("customer.name", new Object[] { 28, "http://www.xxx.com" }, Locale.SIMPLIFIED_CHINESE); System.out.println("Customer name (Chinese) : " + namechinese); } }
因为ApplicationContext也是MessageSource接口的实现,故可以直接调用getMessage()方法。
C、HandlerInterceptor(处理拦截器接口)
org.springframework.web.servlet.HandlerInterceptor
public interface HandlerInterceptor
C.1、LocaleChangeInterceptor(处理拦截器实现)
org.springframework.web.servlet.i18n.LocaleChangeInterceptor
java.lang.Object org.springframework.web.servlet.handler.HandlerInterceptorAdapter org.springframework.web.servlet.i18n.LocaleChangeInterceptor
public class LocaleChangeInterceptor extends HandlerInterceptorAdapter
前面LocaleResolver是自动解析用户的本地化信息locale,除了这个方法外,还可以把一个Interceptor拦截器放到处理器controller中,以便在某种情况下改变locale。(例如:基于请求中参数变更locale)。
C.1.1、基于拦截器的范例
我们再回到《[spring]8 初识MVC和Spring MVC框架》中的范例上,让我们在这个范例的基础上增加动态国际化支持,基于请求参数的动态国际。
范例2
1.Spring配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!-- 定义一个自定义的locale解析器 --> <bean id="localeResolver" class="spring.chapter13.demo1.MyAcceptHeaderLocaleResolver"> </bean> <!-- 定义一个locale的处理拦截器 --> <bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" /> <!-- 定义处理映射HandlerMapping --> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="interceptors" ref="localeChangeInterceptor" /> <property name="mappings"> <props> <prop key="helloWorld.form*">helloWorldAction</prop> <!-- 把对helloWorld.form访问映射到id为helloWorldAction的bean上 --> </props> </property> </bean> <!-- 定义Controller --> <bean id="helloWorldAction" class="spring.chapter13.demo1.HelloWorldAction"> <property name="helloWorld"> <value>Hello Spring World!</value> </property> <property name="viewPage"> <value>sayHello.jsp</value> </property> </bean> </beans>
我们定义了locale解析器(localeResolver)用于解析locale,还定义了一个处理拦截器(HandlerInterceptor)用于拦截url中locale参数。并在处理映射(handlerMapping)中增加了拦截器(Interceptor)。
2.自定义的locale解析器
package spring.chapter13.demo1; import java.util.Locale; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; public class MyAcceptHeaderLocaleResolver extends AcceptHeaderLocaleResolver { private Locale myLocal; public Locale resolveLocale(HttpServletRequest request) { return myLocal; } public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { myLocal = locale; } }
若使用AcceptHeaderLocaleResolver类,程序运行会抛出异常"Cannot change HTTP accept header - use a different locale resolution strategy",根本原因是spring source做了限制,请注意上面的类,该类允许继承,所以需要改写setLocale方法。
3.资源文件的定义
<?xml version="1.0" encoding="UTF-8"?> <beans …> <!--bean的名称必须定义为messageSource --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename"> <value>messages</value> <!-- 前面的是包名,messages是配置文件的前缀 --> </property> </bean> </beans>
4.Controller处理器的处理方法
public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse res) throws Exception { RequestContext requestContext = new RequestContext(req); Locale myLocale = requestContext.getLocale(); String fileName = "../src/spring/chapter13/demo2/bean.xml"; ApplicationContext context = new FileSystemXmlApplicationContext( fileName); String name = ""; name = context.getMessage("ABC", null, myLocale); // 在该方法中处理用户请求 Map model = new HashMap(); model.put("helloWorld", getHelloWorld()); // 将helloWorld属性存 入model中 return new ModelAndView(getViewPage(), model); // 调用getViewPage获取要返回的页面 }
D、用Spring标签实现国际化
Spring标签中实现国际化的标签为spring:message。详细细节见《[spring]14 使用Spring标签库》。
范例3
1.在web.xml中加载spring配置文件
<!-- 使用监听器加载spring配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/bean.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>
2.配置Spring配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!--bean的名称必须定义为messageSource --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename"> <value>spring.chapter13.demo2.messages</value> <!-- 前面的是包名,messages是配置文件的前缀 --> </property> </bean> </beans>
3.定义资源文件
customer.name=david china, age \: {0}, URL \: {1}
ABC=sdsdx
4.JSP文件中使用标签
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%> <%@ taglib uri="http://www.springframework.org/tags/form" prefix="from"%> <html> <head> <title></title> </head> <body> <spring:message code="ABC"></spring:message> <from:form></from:form> </body> </html>