项目中有一个国际化的需求:用户登陆系统时选择语言,登陆后所有文本信息包括页面都转换为相应的语言,每个页面不能单独切换语言,只能登录时选一次。项目基于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参数了。