人人都会AI编程

11.2 国际化与资源加载

更新时间:2026-07-11

随着应用的全球化部署成为常态,国际化(i18n) 不再是一个可选的附加特性,而是产品级的必备能力。Spring 从一开始就内置了对国际化的全面支持,配合统一的资源抽象,让开发者能够以极低的成本实现多语言界面、多区域格式以及灵活的内容加载策略。

11.2.1 Spring 中的国际化基础

Spring 国际化的核心是 MessageSource 接口,它定义了从消息代码和区域信息(Locale)中解析具体消息的策略。最常用的实现是 ResourceBundleMessageSourceReloadableResourceBundleMessageSource(后者支持不重启应用的情况下重新加载资源文件)。

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.jsoncountries_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 的国际化与资源加载体系以 MessageSourceResource 为双核心,覆盖了从多语言文本到任意类型资源的统一管理。借助 Boot 的自动配置,只需遵循文件命名约定即可激活 i18n 能力;结合灵活的 LocaleResolver,可以轻松实现用户区域偏好的持久化。而资源的抽象加载则让代码与底层存储位置解耦,无论是类路径文件、外部目录还是云端对象存储,都能以一致的方式获取。这些基础能力的深度运用,正是一个应用从“功能可用”走向“全球可服务”的关键一步。