国际化
Spring MVC 国际化详解:从原理到实战配置
国际化(Internationalization,简称 i18n)是多语言应用的核心需求,Spring MVC 基于 Java 国际化基础(ResourceBundle),通过 MessageSource(资源加载)和 LocaleResolver(区域解析)两大组件,实现灵活的多语言支持。从 “核心原理→组件解析→配置步骤→实战示例” 四个维度,彻底讲透 Spring MVC 国际化的实现逻辑。
国际化核心原理:Java 基础与 Spring 扩展
Spring MVC 国际化的底层依赖 Java 原生国际化机制,并通过 Spring 组件简化配置与使用:
1. Java 原生国际化基础
Java 通过 ResourceBundle 加载不同语言的资源文件,核心逻辑:
- 资源文件命名:按 “基础名语言代码国家代码.properties” 格式命名(如
messages_zh_CN.properties、messages_en_US.properties); Locale对象:代表语言区域(如Locale.CHINA对应中文,Locale.US对应英文);- 资源加载:
ResourceBundle.getBundle("基础名", Locale)加载对应区域的资源文件,通过getString("key")获取消息。
示例资源文件:
messages_zh_CN.properties(中文):
user.login=登录 user.username=用户名 user.password=密码 user.welcome=欢迎您,{0}!messages_en_US.properties(英文):
user.login=Login user.username=Username user.password=Password user.welcome=Welcome, {0}!
2. Spring MVC 的扩展
Java 原生机制需手动处理 ResourceBundle 和 Locale,Spring MVC 通过以下组件简化流程:
MessageSource:统一管理资源文件,替代ResourceBundle,支持消息格式化(如占位符{0});LocaleResolver:自动解析当前请求的Locale(如从请求头、Cookie、Session 中获取),无需手动指定;MessageTag(JSP 标签):视图层快速获取国际化消息,无需在 Controller 中传递消息。
核心组件 1:MessageSource(资源文件管理)
MessageSource 是 Spring 管理国际化资源的核心接口,负责加载资源文件、解析消息(支持占位符),常用实现类为 ResourceBundleMessageSource(适用于类路径下的 .properties 文件)。
1. MessageSource 接口核心方法
public interface MessageSource {
// 1. 根据 key 和 Locale 获取消息(无占位符)
String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
// 2. 根据 key、占位符参数和 Locale 获取消息
String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
// 3. 通过 MessageSourceResolvable 对象获取消息(复杂场景)
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}
2. ResourceBundleMessageSource 配置
ResourceBundleMessageSource 通过 basename(资源文件基础名)指定资源文件位置,支持多资源文件加载。
(1)XML 配置
在 Spring MVC 配置文件(如 springmvc.xml)中定义 MessageSource Bean:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<!-- 1. 资源文件基础名(多个用逗号分隔,默认从类路径下加载) -->
<!-- 示例:加载 messages.properties、messages_zh_CN.properties 等 -->
<property name="basename" value="i18n/messages"/>
<!-- 2. 默认编码(解决中文乱码,必须设置) -->
<property name="defaultEncoding" value="UTF-8"/>
<!-- 3. 消息不存在时是否抛出异常(默认 true,开发阶段建议 true,生产阶段可设为 false 并返回默认消息) -->
<property name="useCodeAsDefaultMessage" value="false"/>
<!-- 4. 资源文件缓存时间(毫秒,默认永久缓存,开发阶段可设为 0 禁用缓存) -->
<property name="cacheSeconds" value="0"/>
</bean>
(2)Java Config 配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
public class I18nConfig {
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
// 资源文件基础名(类路径下的 i18n 目录)
messageSource.setBasename("i18n/messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setUseCodeAsDefaultMessage(false);
messageSource.setCacheSeconds(0);
return messageSource;
}
}
资源文件目录结构
src/main/resources/
└── i18n/
├── messages.properties # 默认资源文件(无语言指定时使用)
├── messages_zh_CN.properties # 中文资源文件
├── messages_en_US.properties # 英文资源文件
└── messages_ja_JP.properties # 日文资源文件(可选)
3. 代码中使用 MessageSource
在 Controller 或 Service 中注入 MessageSource,通过 getMessage() 方法获取国际化消息:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Locale;
public class I18nController {
// 注入 MessageSource
private MessageSource messageSource;
public String getI18nMessage() {
// 1. 获取当前请求的 Locale(由 LocaleResolver 自动解析,通过 LocaleContextHolder 获取)
Locale locale = LocaleContextHolder.getLocale();
// 2. 获取无占位符的消息(key = "user.login")
String loginMsg = messageSource.getMessage("user.login", null, locale);
// 3. 获取带占位符的消息(key = "user.welcome",占位符参数 = {"张三"})
String welcomeMsg = messageSource.getMessage("user.welcome", new Object[]{"张三"}, locale);
return loginMsg + " | " + welcomeMsg;
}
}
核心组件 2:LocaleResolver(语言区域解析)
LocaleResolver 是 Spring MVC 解析当前请求 Locale 的接口,决定 “使用哪个语言区域的资源文件”。Spring 提供 3 种常用实现类,适用于不同场景。
1. 三种 LocaleResolver 对比
| 实现类 | 核心逻辑 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
AcceptHeaderLocaleResolver |
从请求头 Accept-Language 中获取 Locale(如 zh-CN,zh;q=0.9) |
无需用户手动切换,自动适配浏览器语言 | 无侵入,用户体验好 | 无法手动切换语言(依赖浏览器设置) |
CookieLocaleResolver |
从 Cookie 中获取 Locale(若 Cookie 不存在,使用默认 Locale) | 需要记住用户语言偏好(跨会话生效) | 跨会话保存语言设置(如关闭浏览器后仍生效) | 需要处理 Cookie 过期、清除问题 |
SessionLocaleResolver |
从 HttpSession 中获取 Locale(会话结束后失效) | 临时切换语言(仅当前会话生效) | 实现简单,无 Cookie 依赖 | 会话结束后语言设置丢失 |
2. 配置 LocaleResolver
Spring MVC 默认使用 AcceptHeaderLocaleResolver,若需切换为其他解析器,需在配置中显式定义。
(1)配置 CookieLocaleResolver(XML 示例)
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<!-- 1. Cookie 名称(默认:org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE) -->
<property name="cookieName" value="user_locale"/>
<!-- 2. Cookie 过期时间(秒,-1 表示浏览器关闭后失效) -->
<property name="cookieMaxAge" value="604800"/> <!-- 7 天 -->
<!-- 3. Cookie 路径(默认:/,表示全站生效) -->
<property name="cookiePath" value="/"/>
<!-- 4. 默认 Locale(若 Cookie 和请求头均无,使用此值) -->
<property name="defaultLocale" value="zh_CN"/>
</bean>
(2)配置 SessionLocaleResolver(Java Config 示例)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import java.util.Locale;
public class LocaleConfig {
public LocaleResolver localeResolver() {
SessionLocaleResolver resolver = new SessionLocaleResolver();
// 默认 Locale
resolver.setDefaultLocale(Locale.CHINA);
return resolver;
}
}
3. 手动切换语言:LocaleChangeInterceptor
LocaleResolver 仅负责 “解析” Locale,若需让用户手动切换语言(如点击 “中文 / 英文” 按钮),需配合 LocaleChangeInterceptor(拦截器),通过请求参数修改当前 Locale。
配置步骤:
- 注册 LocaleChangeInterceptor:拦截指定参数(如
lang),根据参数值修改 Locale; - 将拦截器添加到 Spring MVC 拦截器链。
XML 配置示例:
<!-- 1. 配置 LocaleChangeInterceptor -->
<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<!-- 指定触发语言切换的参数名(默认:locale) -->
<property name="paramName" value="lang"/>
</bean>
<!-- 2. 将拦截器添加到拦截器链(所有请求均拦截) -->
<mvc:interceptors>
<ref bean="localeChangeInterceptor"/>
</mvc:interceptors>
<!-- 3. 配置 LocaleResolver(如 CookieLocaleResolver) -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="defaultLocale" value="zh_CN"/>
<property name="cookieMaxAge" value="604800"/>
</bean>
实战:用户切换语言
用户点击链接切换语言,请求参数 lang 指定目标 Locale:
- 切换为中文:
http://localhost:8080/getI18nMessage?lang=zh_CN - 切换为英文:
http://localhost:8080/getI18nMessage?lang=en_US
拦截器会自动将 lang 参数值转为 Locale 对象,并通过 LocaleResolver 保存(Cookie 或 Session),后续请求会使用该 Locale。
视图层使用国际化消息
1. JSP 视图(使用 Spring 标签库)
需先引入 Spring 标签库,通过 <spring:message> 标签获取国际化消息:
(1)引入标签库
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 引入 Spring 标签库 -->
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
(2)使用 <spring:message> 标签
<html>
<head>
<title><spring:message code="user.login"/></title> <!-- 无占位符 -->
</head>
<body>
<h1><spring:message code="user.login"/></h1>
<!-- 带占位符的消息(arguments 指定参数) -->
<p><spring:message code="user.welcome" arguments="张三"/></p>
<!-- 消息不存在时显示默认值 -->
<p><spring:message code="user.unknown" text="未知消息"/></p>
<!-- 语言切换链接 -->
<a href="getI18nMessage?lang=zh_CN">中文</a> |
<a href="getI18nMessage?lang=en_US">English</a>
</body>
</html>
2. Thymeleaf 视图(自然模板)
Thymeleaf 原生支持 Spring 国际化,通过 #{key} 语法获取消息:
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{user.login}"></title>
</head>
<body>
<h1 th:text="#{user.login}"></h1>
<!-- 带占位符的消息(${username} 为 Model 中的变量) -->
<p th:text="#{user.welcome(${username})}"></p>
<!-- 语言切换链接 -->
<a th:href="@{/getI18nMessage(lang='zh_CN')}">中文</a> |
<a th:href="@{/getI18nMessage(lang='en_US')}">English</a>
</body>
</html>
常见问题与解决方案
1. 中文消息乱码
- 原因:资源文件未使用 UTF-8 编码,或
MessageSource未配置defaultEncoding="UTF-8"; - 解决方案:
- 确保资源文件(
.properties)的编码为 UTF-8(IDE 中设置,如 IDEA:File → Settings → File Encodings → Properties Files 设为 UTF-8); - 在
ResourceBundleMessageSource中显式设置defaultEncoding="UTF-8"。
- 确保资源文件(
2. LocaleChangeInterceptor 不生效
- 原因:
- 拦截器未注册到 Spring MVC 拦截器链;
- 请求参数名与拦截器的
paramName不匹配; LocaleResolver配置错误(如 Bean ID 非localeResolver);
- 解决方案:
- 确保通过
<mvc:interceptors>或WebMvcConfigurer.addInterceptors()注册拦截器; - 检查请求参数名与
paramName一致(如默认locale或自定义lang); - 确保
LocaleResolver的 Bean ID 为localeResolver(Spring 自动查找此 ID)。
- 确保通过
3. 消息占位符不生效
- 原因:
getMessage()方法的args参数传递错误(如参数数量与占位符数量不匹配); - 解决方案:
- 确保
args数组长度与占位符数量一致(如user.welcome有 1 个占位符,args需为new Object[]{"张三"}); - JSP 中使用
<spring:message arguments="参数1,参数2"/>,多个参数用逗号分隔。
- 确保
总结与最佳实践
1. 组件选择建议
| 需求场景 | 推荐组合 |
|---|---|
| 自动适配浏览器语言,无需手动切换 | AcceptHeaderLocaleResolver + ResourceBundleMessageSource |
| 需要用户手动切换语言,且跨会话生效 | CookieLocaleResolver + LocaleChangeInterceptor + ResourceBundleMessageSource |
| 需要用户手动切换语言,仅当前会话生效 | SessionLocaleResolver + LocaleChangeInterceptor + ResourceBundleMessageSource |
2. 最佳实践
- 资源文件管理:
- 按模块划分资源文件(如
i18n/user/messages.properties、i18n/order/messages.properties),避免单个文件过大; - 统一消息 key 命名规范(如
模块.功能.描述,如user.login.btn、order.pay.success)。
- 按模块划分资源文件(如
- 开发阶段配置:
- 设置
cacheSeconds=0禁用资源文件缓存,避免修改后需重启应用; - 设置
useCodeAsDefaultMessage=false,消息不存在时抛出异常,便于早期发现问题。
- 设置
- 生产阶段配置:
- 启用资源文件缓存(
cacheSeconds=3600,1 小时),提升性能; - 处理
NoSuchMessageException异常,返回默认消息,避免页面报错
- 启用资源文件缓存(