0%

国际化

Spring MVC 国际化详解:从原理到实战配置

国际化(Internationalization,简称 i18n)是多语言应用的核心需求,Spring MVC 基于 Java 国际化基础(ResourceBundle),通过 MessageSource(资源加载)和 LocaleResolver(区域解析)两大组件,实现灵活的多语言支持。从 “核心原理→组件解析→配置步骤→实战示例” 四个维度,彻底讲透 Spring MVC 国际化的实现逻辑。

国际化核心原理:Java 基础与 Spring 扩展

Spring MVC 国际化的底层依赖 Java 原生国际化机制,并通过 Spring 组件简化配置与使用:

1. Java 原生国际化基础

Java 通过 ResourceBundle 加载不同语言的资源文件,核心逻辑:

  1. 资源文件命名:按 “基础名语言代码国家代码.properties” 格式命名(如 messages_zh_CN.propertiesmessages_en_US.properties);
  2. Locale 对象:代表语言区域(如 Locale.CHINA 对应中文,Locale.US 对应英文);
  3. 资源加载ResourceBundle.getBundle("基础名", Locale) 加载对应区域的资源文件,通过 getString("key") 获取消息。
示例资源文件:
  • messages_zh_CN.properties(中文):

    1
    2
    3
    4
    user.login=登录
    user.username=用户名
    user.password=密码
    user.welcome=欢迎您,{0}!
  • messages_en_US.properties(英文):

    1
    2
    3
    4
    user.login=Login
    user.username=Username
    user.password=Password
    user.welcome=Welcome, {0}!

2. Spring MVC 的扩展

Java 原生机制需手动处理 ResourceBundleLocale,Spring MVC 通过以下组件简化流程:

  • MessageSource:统一管理资源文件,替代 ResourceBundle,支持消息格式化(如占位符 {0});
  • LocaleResolver:自动解析当前请求的 Locale(如从请求头、Cookie、Session 中获取),无需手动指定;
  • MessageTag(JSP 标签):视图层快速获取国际化消息,无需在 Controller 中传递消息。

核心组件 1:MessageSource(资源文件管理)

MessageSource 是 Spring 管理国际化资源的核心接口,负责加载资源文件、解析消息(支持占位符),常用实现类为 ResourceBundleMessageSource(适用于类路径下的 .properties 文件)。

1. MessageSource 接口核心方法

1
2
3
4
5
6
7
8
9
10
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;

@Configuration
public class I18nConfig {
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
// 资源文件基础名(类路径下的 i18n 目录)
messageSource.setBasename("i18n/messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setUseCodeAsDefaultMessage(false);
messageSource.setCacheSeconds(0);
return messageSource;
}
}
资源文件目录结构
1
2
3
4
5
6
src/main/resources/
└── i18n/
├── messages.properties # 默认资源文件(无语言指定时使用)
├── messages_zh_CN.properties # 中文资源文件
├── messages_en_US.properties # 英文资源文件
└── messages_ja_JP.properties # 日文资源文件(可选)

3. 代码中使用 MessageSource

在 Controller 或 Service 中注入 MessageSource,通过 getMessage() 方法获取国际化消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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;

@RestController
public class I18nController {
// 注入 MessageSource
@Autowired
private MessageSource messageSource;

@GetMapping("/getI18nMessage")
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 示例)
1
2
3
4
5
6
7
8
9
10
11
12
13
<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 示例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;

@Configuration
public class LocaleConfig {
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver resolver = new SessionLocaleResolver();
// 默认 Locale
resolver.setDefaultLocale(Locale.CHINA);
return resolver;
}
}

3. 手动切换语言:LocaleChangeInterceptor

LocaleResolver 仅负责 “解析” Locale,若需让用户手动切换语言(如点击 “中文 / 英文” 按钮),需配合 LocaleChangeInterceptor(拦截器),通过请求参数修改当前 Locale。

配置步骤:
  1. 注册 LocaleChangeInterceptor:拦截指定参数(如 lang),根据参数值修改 Locale;
  2. 将拦截器添加到 Spring MVC 拦截器链
XML 配置示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 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)引入标签库
1
2
3
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 引入 Spring 标签库 -->
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
(2)使用 <spring:message> 标签
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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} 语法获取消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<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"
  • 解决方案:
    1. 确保资源文件(.properties)的编码为 UTF-8(IDE 中设置,如 IDEA:File → Settings → File Encodings → Properties Files 设为 UTF-8);
    2. ResourceBundleMessageSource 中显式设置 defaultEncoding="UTF-8"

2. LocaleChangeInterceptor 不生效

  • 原因:
    1. 拦截器未注册到 Spring MVC 拦截器链;
    2. 请求参数名与拦截器的 paramName 不匹配;
    3. LocaleResolver 配置错误(如 Bean ID 非 localeResolver);
  • 解决方案:
    1. 确保通过 <mvc:interceptors>WebMvcConfigurer.addInterceptors() 注册拦截器;
    2. 检查请求参数名与 paramName 一致(如默认 locale 或自定义 lang);
    3. 确保 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. 最佳实践

  1. 资源文件管理
    • 按模块划分资源文件(如 i18n/user/messages.propertiesi18n/order/messages.properties),避免单个文件过大;
    • 统一消息 key 命名规范(如 模块.功能.描述,如 user.login.btnorder.pay.success)。
  2. 开发阶段配置
    • 设置 cacheSeconds=0 禁用资源文件缓存,避免修改后需重启应用;
    • 设置 useCodeAsDefaultMessage=false,消息不存在时抛出异常,便于早期发现问题。
  3. 生产阶段配置
    • 启用资源文件缓存(cacheSeconds=3600,1 小时),提升性能;
    • 处理 NoSuchMessageException 异常,返回默认消息,避免页面报错

欢迎关注我的其它发布渠道