关键词:Spring MVC、国际化(i18n)、MessageSource、LocaleResolver、LocaleChangeInterceptor、多语言Web、资源文件
摘要:本文将以"餐厅菜单"为类比,深入浅出地讲解Spring MVC国际化(i18n)的核心原理与实现步骤。我们会从"为什么需要国际化"讲起,拆解Locale(语言标签)、MessageSource(消息仓库)、LocaleResolver(语言解析器)等核心概念,再通过完整的项目实战(含代码、配置、页面)演示如何让Web应用支持多语言切换。最终,你将掌握从"配置到部署"的全流程,学会用Spring MVC快速搭建支持中文、英文等多语言的Web应用。
目的:帮助Java Web开发者理解Spring MVC国际化的底层逻辑,掌握多语言Web应用的开发技巧。
范围:覆盖Spring MVC中国际化的核心组件(MessageSource、LocaleResolver、拦截器)、配置方式(XML/注解)、页面集成(JSTL标签)及实战案例(登录页面多语言切换)。
本文采用"概念→原理→实战"的递进结构:
用"餐厅菜单"的故事引出国际化需求;拆解核心概念(Locale、MessageSource、LocaleResolver)及它们的关系;讲解Spring MVC中国际化的配置步骤(资源文件、组件配置);通过完整项目(登录页面)演示多语言切换的实现;探讨实际应用场景、工具推荐及未来趋势。
zh_CN,英文-美国是
en_US),用来标识用户的语言偏好。MessageSource:Spring中的"消息仓库",存储不同Locale对应的文本(比如
login.title在
zh_CN中是"登录页面",在
en_US中是"Login Page")。LocaleResolver:“语言解析器”,负责从用户请求中获取Locale(比如从Cookie、Session或请求参数中取)。
messages_zh_CN.properties存中文,
messages_en_US.properties存英文)。LocaleChangeInterceptor:“语言切换拦截器”,允许用户通过请求参数(比如
?lang=en_US)切换Locale。
假设你开了一家跨国餐厅,客人来自中国、美国、日本。为了让客人舒服,你需要:
识别客人的语言:比如看到黄皮肤、说中文的客人,给中文菜单;看到蓝眼睛、说英文的客人,给英文菜单。准备多语言菜单:把"宫保鸡丁"翻译成"Kung Pao Chicken",“结账"翻译成"Check Out”,存放在不同的菜单本里。切换菜单:如果客人突然说"给我英文菜单",你要能快速切换。这个场景和Web应用的国际化需求完全一致:
客人=用户;语言偏好=Locale;多语言菜单=MessageSource(资源文件);服务员递菜单=LocaleResolver(解析Locale并获取对应消息);客人要求换菜单=LocaleChangeInterceptor(切换Locale)。接下来,我们把这些"餐厅元素"对应到Spring MVC的国际化组件中。
Locale就像客人的"语言身份证",上面写着"中文-中国"或"英文-美国"。比如:
中国客人的Locale是
zh_CN(zh=中文,CN=中国);美国客人的Locale是
en_US(en=英文,US=美国);日本客人的Locale是
ja_JP(ja=日文,JP=日本)。
Spring MVC会用这个"身份证"来判断给用户显示哪种语言的内容。
MessageSource是Spring中的"消息仓库",就像餐厅里存菜单的文件夹:
文件夹里有多个菜单本(资源文件),比如
messages_zh_CN.properties(中文菜单)、
messages_en_US.properties(英文菜单);每个菜单本里有"键-值"对,比如中文菜单里
login.title=登录页面,英文菜单里
login.title=Login Page;当服务员(LocaleResolver)拿到客人的Locale(比如
en_US),就会从对应的菜单本(
messages_en_US.properties)里取出对应的内容(
Login Page)。
LocaleResolver是"递菜单的服务员",负责从用户请求中获取Locale(语言偏好)。Spring MVC提供了几种常见的"服务员":
CookieLocaleResolver:把Locale存在Cookie里(比如用户第一次访问时,服务员记下来,下次直接拿);SessionLocaleResolver:把Locale存在Session里(用户登录后有效,退出登录就消失);AcceptHeaderLocaleResolver:从请求头
Accept-Language中取Locale(比如浏览器设置的语言);FixedLocaleResolver:固定Locale(比如强制所有用户都看中文)。
比如,用CookieLocaleResolver的话,服务员会说:“这位客人上次来用的是英文,这次还给他英文菜单。”
LocaleChangeInterceptor是"换菜单的按钮",允许用户主动切换语言。比如餐厅里有个"切换语言"的按钮,客人点一下"英文",服务员就会换英文菜单。在Web应用中,用户点击
?lang=en_US的链接,拦截器就会把Locale改成
en_US。
我们用"餐厅服务流程"来总结核心概念的关系:
客人进店(用户请求):用户访问Web应用(比如
http://localhost:8080/login);服务员问语言(LocaleResolver获取Locale):服务员(LocaleResolver)查看客人的"语言身份证"(比如从Cookie中取
zh_CN);拿对应菜单(MessageSource取消息):服务员从文件夹(MessageSource)里拿出中文菜单(
messages_zh_CN.properties),找到"登录页面"(
login.title);给客人看菜单(显示页面):把"登录页面"显示在用户的浏览器上;客人换菜单(LocaleChangeInterceptor切换Locale):客人点击"英文"链接(
?lang=en_US),拦截器(LocaleChangeInterceptor)把客人的"语言身份证"改成
en_US,服务员下次就给英文菜单了。
用户请求 → DispatcherServlet(Spring MVC前端控制器)
→ LocaleResolver(解析Locale,比如从Cookie取zh_CN)
→ MessageSource(根据zh_CN从messages_zh_CN.properties取消息)
→ 视图解析器(比如JSP)
→ 显示多语言页面给用户
graph TD
A[用户访问登录页面] --> B[DispatcherServlet接收请求]
B --> C[LocaleResolver获取Locale(比如zh_CN)]
C --> D[MessageSource根据Locale取消息(login.title=登录页面)]
D --> E[JSP页面用fmt标签显示消息]
E --> F[返回中文登录页面给用户]
F --> G[用户点击"切换英文"链接(?lang=en_US)]
G --> H[LocaleChangeInterceptor拦截请求,修改Locale为en_US]
H --> C[LocaleResolver获取新的Locale(en_US)]
C --> D[MessageSource取英文消息(login.title=Login Page)]
D --> E[JSP显示英文页面]
E --> I[返回英文登录页面给用户]
首先,我们需要在
src/main/resources目录下创建3个资源文件(默认、中文、英文):
资源文件内容示例:
messages_zh_CN.properties(中文):
login.title=登录页面
login.username=用户名
login.password=密码
login.button=登录
login.switch=切换英文
messages_en_US.properties(英文):
login.title=Login Page
login.username=Username
login.password=Password
login.button=Login
login.switch=Switch to Chinese
messages.properties(默认,可选):
# 默认用中文
login.title=登录页面
注意:资源文件的编码要设置为UTF-8(避免中文乱码)。在IntelliJ IDEA中,可以右键资源文件→"Properties"→"File Encodings"→选择"UTF-8"。
接下来,在Spring MVC的配置文件(比如
spring-servlet.xml)中配置MessageSource,告诉Spring where to find the 资源文件(菜单本)。
XML配置示例:
<!-- 配置MessageSource(消息仓库) -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<!-- 资源文件前缀(比如messages_zh_CN.properties的前缀是messages) -->
<property name="basename" value="classpath:messages"/>
<!-- 资源文件编码(UTF-8避免乱码) -->
<property name="defaultEncoding" value="UTF-8"/>
<!-- 缓存时间(秒,-1表示不缓存,开发时用0,生产时用3600) -->
<property name="cacheSeconds" value="0"/>
</bean>
注解配置示例(Spring Boot):
如果用Spring Boot,可以在
application.properties中配置:
# 消息源配置
spring.messages.basename=classpath:messages
spring.messages.encoding=UTF-8
spring.messages.cache-duration=0
选择一个"服务员"(LocaleResolver),比如CookieLocaleResolver(把Locale存在Cookie里,持久化)。
XML配置示例:
<!-- 配置LocaleResolver(语言解析器):Cookie方式 -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<!-- 默认Locale(中文-中国) -->
<property name="defaultLocale" value="zh_CN"/>
<!-- Cookie名称(可选,默认是org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE) -->
<property name="cookieName" value="lang"/>
<!-- Cookie有效期(秒,-1表示会话结束后失效) -->
<property name="cookieMaxAge" value="3600"/>
</bean>
注解配置示例(Spring Boot):
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 配置CookieLocaleResolver
@Bean
public LocaleResolver localeResolver() {
CookieLocaleResolver resolver = new CookieLocaleResolver();
resolver.setDefaultLocale(Locale.CHINA); // 默认中文
resolver.setCookieName("lang"); // Cookie名称
resolver.setCookieMaxAge(3600); // 有效期1小时
return resolver;
}
}
添加"换菜单的按钮"(LocaleChangeInterceptor),允许用户通过请求参数(比如
?lang=en_US)切换Locale。
XML配置示例:
<!-- 配置拦截器链 -->
<mvc:interceptors>
<!-- 语言切换拦截器 -->
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<!-- 请求参数名(比如?lang=en_US) -->
<property name="paramName" value="lang"/>
</bean>
</mvc:interceptors>
注解配置示例(Spring Boot):
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 配置LocaleChangeInterceptor
@Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang"); // 请求参数名
registry.addInterceptor(interceptor);
}
}
最后,在JSP页面中用JSTL的
fmt标签从MessageSource中获取消息(就像服务员把菜单递给客人)。
登录页面(login.jsp)示例:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <!-- 导入fmt标签 -->
<html>
<head>
<title><fmt:message key="login.title"/></title> <!-- 显示登录页面标题 -->
</head>
<body>
<h1><fmt:message key="login.title"/></h1>
<form action="/login" method="post">
<div>
<label><fmt:message key="login.username"/>:</label> <!-- 显示用户名标签 -->
<input type="text" name="username">
</div>
<div>
<label><fmt:message key="login.password"/>:</label> <!-- 显示密码标签 -->
<input type="password" name="password">
</div>
<div>
<button type="submit"><fmt:message key="login.button"/></button> <!-- 显示登录按钮 -->
</div>
</form>
<!-- 切换语言链接(用lang参数) -->
<a href="?lang=${pageContext.response.locale.language == 'zh' ? 'en_US' : 'zh_CN'}">
<fmt:message key="login.switch"/> <!-- 显示切换语言文本 -->
</a>
</body>
</html>
说明:
<fmt:message key="login.title"/>:从MessageSource中获取
login.title对应的消息(中文是"登录页面",英文是"Login Page");切换语言链接:
?lang=en_US会触发LocaleChangeInterceptor,把Locale改成
en_US;
${pageContext.response.locale.language}:获取当前Locale的语言(比如
zh或
en),用来动态显示切换链接的文本(比如中文页面显示"切换英文",英文页面显示"Switch to Chinese")。
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── controller/
│ │ └── LoginController.java(登录控制器)
│ ├── resources/
│ │ ├── messages.properties(默认资源文件)
│ │ ├── messages_zh_CN.properties(中文资源文件)
│ │ └── messages_en_US.properties(英文资源文件)
│ └── webapp/
│ ├── WEB-INF/
│ │ ├── spring-servlet.xml(Spring MVC配置文件)
│ │ └── web.xml(Web应用配置文件)
│ └── login.jsp(登录页面)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 配置DispatcherServlet(Spring MVC前端控制器) -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-servlet.xml</param-value> <!-- Spring MVC配置文件路径 -->
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern> <!-- 处理所有请求 -->
</servlet-mapping>
</web-app>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 组件扫描(扫描@Controller注解的类) -->
<context:component-scan base-package="com.example.controller"/>
<!-- 开启Spring MVC注解驱动 -->
<mvc:annotation-driven/>
<!-- 配置视图解析器(JSP视图解析器) -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"/> <!-- JSP页面前缀(比如/login.jsp) -->
<property name="suffix" value=".jsp"/> <!-- JSP页面后缀 -->
</bean>
<!-- 配置MessageSource(消息仓库) -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:messages"/>
<property name="defaultEncoding" value="UTF-8"/>
<property name="cacheSeconds" value="0"/>
</bean>
<!-- 配置LocaleResolver(Cookie方式) -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="defaultLocale" value="zh_CN"/>
<property name="cookieName" value="lang"/>
<property name="cookieMaxAge" value="3600"/>
</bean>
<!-- 配置拦截器(语言切换拦截器) -->
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="lang"/>
</bean>
</mvc:interceptors>
</beans>
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
// 处理GET请求,返回登录页面
@GetMapping("/login")
public String showLoginPage() {
return "login"; // 对应login.jsp页面(视图解析器会加上前缀/和后缀.jsp)
}
}
login.title=登录页面
login.username=用户名
login.password=密码
login.button=登录
login.switch=切换英文
messages_en_US.properties(英文):
login.title=Login Page
login.username=Username
login.password=Password
login.button=Login
login.switch=Switch to Chinese
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<title><fmt:message key="login.title"/></title>
</head>
<body>
<h1><fmt:message key="login.title"/></h1>
<form action="/login" method="post">
<div>
<label><fmt:message key="login.username"/>:</label>
<input type="text" name="username">
</div>
<div>
<label><fmt:message key="login.password"/>:</label>
<input type="password" name="password">
</div>
<div>
<button type="submit"><fmt:message key="login.button"/></button>
</div>
</form>
<div>
<a href="?lang=${pageContext.response.locale.language == 'zh' ? 'en_US' : 'zh_CN'}">
<fmt:message key="login.switch"/>
</a>
</div>
</body>
</html>
LoginController的
showLoginPage方法处理
/login的GET请求,返回
login(视图解析器会解析为
/login.jsp);资源文件:存储多语言文本,
login.title对应不同语言的标题;页面:用
fmt:message标签从MessageSource中获取消息,切换语言链接用
?lang参数触发拦截器;配置文件:
spring-servlet.xml配置了MessageSource(找资源文件)、LocaleResolver(用Cookie存Locale)、拦截器(处理语言切换)。
http://localhost:8080/login,默认显示中文登录页面;切换语言:点击"切换英文"链接,页面会切换到英文登录页面(URL变成
http://localhost:8080/login?lang=en_US);验证Cookie:打开浏览器的开发者工具(F12)→"Application"→"Cookies",可以看到
langCookie的值是
en_US(有效期1小时)。
Spring MVC国际化广泛应用于需要支持多语言的Web应用:
电商网站:比如亚马逊、淘宝的国际版,支持中文、英文、日文等;企业官网:比如华为、联想的官网,面向全球用户;SaaS应用:比如钉钉、飞书的国际版,支持不同地区的语言;政府网站:比如中国政府网的英文版,面向外国用户。
MessageSource,从数据库取消息),支持动态修改消息(不需要重启服务器);AI翻译:结合AI翻译API(比如Google Translate API),自动翻译消息(减少人工翻译成本);多语言SEO:优化多语言页面的SEO(比如用
hreflang标签告诉搜索引擎不同语言的页面)。
ReloadableResourceBundleMessageSource的
cacheSeconds属性);语言差异:不同语言的文本长度不同(比如中文"登录"比英文"Login"短),需要调整页面布局(比如用弹性布局);地区差异:除了语言,还有地区习惯(比如日期格式:中文是"2024-05-20",英文是"May 20, 2024"),需要用
fmt:formatDate标签处理。
zh_CN、
en_US);MessageSource:存储多语言文本的"消息仓库"(资源文件);LocaleResolver:解析用户Locale的"服务员"(比如CookieLocaleResolver);LocaleChangeInterceptor:允许用户切换Locale的"按钮"(比如
?lang=en_US)。
用户请求→DispatcherServlet→LocaleResolver获取Locale→MessageSource取对应消息→显示多语言页面→用户点击切换链接→LocaleChangeInterceptor修改Locale→重复上述流程。
message表,字段有
key、
locale、
value),需要自定义
MessageSource吗?如何实现?思考题二:如果用Spring Boot,如何用注解配置
MessageSource和
LocaleResolver?(提示:用
@Bean注解)思考题三:如何在REST接口中支持国际化?(比如返回
{"message": "登录成功"}或
{"message": "Login Success"})(提示:用
@RequestHeader获取
Accept-Language头,或用
LocaleContextHolder获取当前Locale)
解答:检查资源文件的编码是否为UTF-8(IntelliJ IDEA中右键资源文件→"Properties"→"File Encodings"→选择"UTF-8");同时,
MessageSource的
defaultEncoding属性要设置为
UTF-8。
解答:检查
LocaleChangeInterceptor的
paramName属性是否与链接中的参数名一致(比如
paramName="lang",链接是
?lang=en_US);另外,要确保拦截器配置到了Spring MVC的拦截器链中(比如
mvc:interceptors标签)。
No message found under key 'login.title')?解答:检查资源文件的路径是否正确(比如
basename是
classpath:messages,资源文件要放在
src/main/resources目录下);另外,资源文件的命名是否符合规范(比如
messages_zh_CN.properties)。
结语:国际化是Web应用走向全球的必经之路,Spring MVC提供了一套完整的解决方案。通过本文的学习,你已经掌握了Spring MVC国际化的核心原理和实现步骤,接下来可以尝试在自己的项目中添加多语言支持,让应用覆盖更多用户!