项目中有一个国际化的需求:用户登陆系统时选择语言,登陆后所有文本信息包括页面都转换为相应的语言,每个页面不能单独切换语言,只能登录时选一次。项目基于shiro spring mvc搭建,下面描述一下实现思路。
首先,spring国际化的过程:
STEP 1.
在beans.xml中配置messageSource bean:
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <!-- <property name="basename" value="classpath:i18n/messages" /> --> <!-- <property name="defaultEncoding" value="UTF-8"></property> --> <property name="basenames"> <list> <value>classpath:i18n/messages</value> </list> </property> </bean>
i18n/下应该有messages_en_US.properties和messages_zh_CN.properties之类的国际化文件。这样,XmlWebApplicationContext就有了基本的国际化能力了,调用getMessage()方法即可获取默认语言的文本了,要想获取其他语言,还需要提供Locale参数。
STEP 2.
配置spring-mvc.xml中localeResolver bean:
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"> <property name="defaultLocale" value="en_US" /> </bean>
这样,DispatcherServlet就装配上了localeResolver。SessionLocaleResolver的功能是在session中查找一个特殊的属性值,这个值是用户选择的Locale,如果发现了,就以这个Locale作为本次请求的Locale。实现中,可以在登陆时向session中设置用户所选择的Locale,配上SessionLocaleResolver就可以让DispatcherServlet在这个用户每次请求时都使用这个Locale了,这样,getMessage()的Locale参数就有了。
STEP 3.
前两步不算难,难的是如何跟shiro集成起来。
我使用了
<bean id="localeFilter" class="mycustom.LocaleFilter"/> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/login" /> <property name="successUrl" value="/" /> <property name="unauthorizedUrl" value="/unauthorized" /> <property name="filterChainDefinitions"> <value> /login = localeFilter, authc /** = user </value> </property> </bean>
ShiroFilterFactoryBean有个功能,可以自动发现Filter,并将其加入Filter链中。这里有个感念,shiro中认证是用javax.servlet.Filter接口实现的,因此web中普通的Filter可以跟shiro的认证链集成的很好,至少接口很熟悉。上面自定义了一个localeFilter bean,并配置为/login = localeFilter, authc,这就是说先执行我们的localeFilter,再执行shiro默认的authc,这样,就找到了一个向session中存用户选择的语言的地方----localeFilter。
代码很简单:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (request.getParameter("locale") != null){ HttpServletRequest req = (HttpServletRequest) request; HttpSession session = req.getSession(); Locale locale = null; switch (request.getParameter("locale")){ case "zh_CN": locale = Locale.CHINA; break; case "en_US": locale = Locale.US; break; } session.setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, locale); } chain.doFilter(request, response); }
代码的功能是向session中设置用户选择的语言。注意,localeFilter不能放在authc后面,因为默认的authc实现当登陆成功后,后面的Filter链不会执行,只能放在前面。这样,当用户登陆时,就可以选择语言了。
为什么要这么麻烦呢,直接从request获取locale参数不就好了吗?这是不行的,因为当shiro登陆成功后,会重定向请求,定向到登陆成功页url,重定向之后request就获取不到在登陆时传的locale参数了。