随着应用的全球化部署成为常态,国际化(i18n) 不再是一个可选的附加特性,而是产品级的必备能力。Spring 从一开始就内置了对国际化的全面支持,配合统一的资源抽象,让开发者能够以极低的成本实现多语言界面、多区域格式以及灵活的内容加载策略。
11.2.1 Spring 中的国际化基础
Spring 国际化的核心是 MessageSource 接口,它定义了从消息代码和区域信息(Locale)中解析具体消息的策略。最常用的实现是 ResourceBundleMessageSource 和 ReloadableResourceBundleMessageSource(后者支持不重启应用的情况下重新加载资源文件)。
1. 配置 MessageSource
在 Spring Boot 中,只要在 src/main/resources 下放置符合命名规范的文件,框架就会自动创建一个 MessageSource Bean。默认的 basename 为 messages,即它会寻找以下文件:
messages.properties— 默认回退消息messages_zh_CN.properties— 简体中文messages_en_US.properties— 美国英语messages_ja.properties— 日语
如果需要自定义 basename 或编码,可在 application.yml 中调整:
spring:
messages:
basename: i18n/messages # 自动扫描 i18n/ 目录下以 messages 开头的资源文件
encoding: UTF-8
cache-duration: 3600 # 缓存时间(秒),-1 表示永久缓存
2. 消息文件示例
在 i18n/messages.properties 中定义默认(英文)文本:
user.welcome=Welcome, {0}!
user.notfound=User {0} not found.
order.title=Order Details
在 i18n/messages_zh_CN.properties 中:
user.welcome=欢迎您,{0}!
user.notfound=用户{0}不存在。
order.title=订单详情
花括号中的 {0} 是占位符,用于动态传参。
3. 注入并使用 MessageSource
在业务代码中通过 @Autowired 直接使用:
@RestController
public class GreetingController {
private final MessageSource messageSource;
public GreetingController(MessageSource messageSource) {
this.messageSource = messageSource;
}
@GetMapping("/greet")
public String greet(@RequestParam String name, Locale locale) {
return messageSource.getMessage("user.welcome", new Object[]{name}, locale);
}
}
Controller 方法中的 Locale locale 参数会被 Spring 自动解析(通过 LocaleResolver,见后文)。测试时传入不同的 Accept-Language 请求头,即可返回对应的语言文本。
11.2.2 区域解析器:LocaleResolver
应用如何判断当前请求的语言?这项工作由 LocaleResolver 完成。Spring 提供了几种实现,可根据场景灵活选用:
1. AcceptHeaderLocaleResolver(默认)
基于 HTTP 请求头 Accept-Language 解析。无需额外配置,适用于大多数场景,但缺点是语言选择完全依赖浏览器设置,无法由应用端显式覆盖。
2. SessionLocaleResolver
将区域信息保存在用户 HTTP 会话中。用户可以在应用中切换语言,该选择在整个会话期间保持有效。配置方式:
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver resolver = new SessionLocaleResolver();
resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE); // 默认中文
return resolver;
}
随后可以通过 Controller 提供一个语言切换端点:
@GetMapping("/changeLang")
public String changeLang(@RequestParam String lang, HttpServletRequest request,
HttpServletResponse response) {
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
if (localeResolver != null) {
localeResolver.setLocale(request, response, new Locale(lang));
}
return "Language changed to " + lang;
}
3. CookieLocaleResolver
将区域信息存储在客户端 Cookie 中,持久性更强,即使关闭浏览器也不会丢失偏好。配置与 SessionLocaleResolver 类似,只需修改实现类。
4. FixedLocaleResolver
固定区域,通常用于测试或简单系统。
11.2.3 在模板与视图中使用国际化
Thymeleaf 等模板引擎与 Spring 的国际化深度整合。在 Thymeleaf 中,直接使用 #messages 工具对象:
<h1 th:text="#{order.title}">Order Details</h1>
<p th:text="#{user.welcome(${username})}">Welcome, user!</p>
#{...} 语法会自动调用 MessageSource,并根据当前 Locale 返回相应文本。无需在控制器中显式处理,页面直接享受多语言能力。
11.2.4 资源加载体系:Resource 抽象
除了消息文件,国际化还常常涉及不同区域的静态资源(如图片、PDF)。Spring 提供了统一的资源抽象接口 Resource,并通过 ResourceLoader 自动根据路径前缀选择具体实现。
1. 资源路径前缀
| 前缀 | 说明 | 示例 |
|---------------|--------------------------|--------------------------------|
| classpath: | 类路径下的资源 | classpath:static/logo.png |
| file: | 文件系统中的资源 | file:/var/data/config.yml |
| http:/https: | 远程资源 | https://cdn.example.com/img |
| 无前缀 | 默认由 ResourceLoader 决定 | - |
2. 注入 Resource 对象
可以直接将资源注入到 Bean 中,Spring 会根据字符串路径自动创建对应的 Resource 实例:
@Value("classpath:data/sample.csv")
private Resource sampleCsv;
public void load() throws IOException {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(sampleCsv.getInputStream()))) {
// 处理资源内容
}
}
3. 区域相关的资源加载
虽然 Resource 本身不感知语言,但我们可以结合 MessageSource 或自定义加载器实现区域资源切换。例如,不同语言的国家选择列表 JSON 文件可以命名为 countries_zh.json、countries_en.json,在代码中动态构造路径:
public Resource getCountryResource(Locale locale) {
String lang = locale.getLanguage();
String path = String.format("classpath:data/countries_%s.json", lang);
return resourceLoader.getResource(path);
}
这样,用户看到的选项语言便会与其区域匹配。
11.2.5 实用建议与常见陷阱
1. 消息键命名规范
使用分层的命名方式可以让大型项目中成千上万的键保持有序。例如:
order.confirm.button=Confirm Order
order.status.PENDING=Pending
order.status.SHIPPED=Shipped
error.validation.required={0} is required
清晰的层级既便于查找,也避免键冲突。
2. 编码问题
.properties 文件传统上以 ISO-8859-1 编码,中文等字符需转义。Spring Boot 默认使用 UTF-8 读取,无需手动 Unicode 转义,但建议在 IDE 和构建工具中确保资源文件以 UTF-8 存储。
3. 缺失消息的处理
默认情况下,如果找不到对应的键,getMessage 方法会抛出 NoSuchMessageException。可以让它返回默认值或键本身,避免页面出现错误码:
messageSource.getMessage(code, args, "[" + code + "]", locale);
使用三参数重载,第三参数为默认值,找不到键时返回方括号内的键名便于排查。
4. 动态刷新消息
如果需要在运行时修改消息而不重启,可以使用 ReloadableResourceBundleMessageSource 并设置 cacheSeconds 为一个较小值(甚至 0)。但频繁刷新会带来 I/O 开销,一般建议部署时通过配置中心下发完整资源包。
5. 前端国际化与后端职责划分
单页应用(SPA)通常自己管理 i18n 资源,后端只需对异常提示、邮件模板、报告等服务器生成内容进行国际化。两者通过 HTTP 头或 URL 参数传递语言偏好,形成前后端分离的协作模式。
11.2.6 小结
Spring 的国际化与资源加载体系以 MessageSource 和 Resource 为双核心,覆盖了从多语言文本到任意类型资源的统一管理。借助 Boot 的自动配置,只需遵循文件命名约定即可激活 i18n 能力;结合灵活的 LocaleResolver,可以轻松实现用户区域偏好的持久化。而资源的抽象加载则让代码与底层存储位置解耦,无论是类路径文件、外部目录还是云端对象存储,都能以一致的方式获取。这些基础能力的深度运用,正是一个应用从“功能可用”走向“全球可服务”的关键一步。