国际化 LocaleResolver 与消息解析
同一套页面,中文用户看中文、英文用户看英文——国际化(i18n)需要解决两件事:当前请求该用哪种语言(Locale 从哪来),以及消息文案按 Locale 去哪查(MessageSource)。
第一步:准备消息文件
在 classpath 下放一组以 basename 命名的 properties,Locale 用语言后缀区分:
# messages.properties(默认语言,无后缀)
welcome=Welcome, {0}!
order.total=Total: {0}
# messages_zh_CN.properties(简体中文)
welcome=欢迎,{0}!
order.total=合计:{0}
中文内容注意 UTF-8 编码与 native2ascii(或用 IDE 的 properties 编辑器自动转码),容器读取编码问题以官方文档为准。
第二步:声明 MessageSource
Spring 的 MessageSource 负责按 key + Locale 查文案,标准实现是 ResourceBundleMessageSource:
@Configuration
public class I18nConfig {
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasename("messages"); // 对应 messages*.properties
source.setDefaultEncoding("UTF-8");
source.setFallbackToSystemLocale(false); // 找不到语言包时回退默认 messages
return source;
}
}
// 说明:Spring Boot 会自动配置名为 messageSource 的 MessageSource,可覆盖属性定制
第三步:Locale 从哪来——LocaleResolver
LocaleResolver 决定每个请求的 Locale。Spring MVC 默认使用 AcceptHeaderLocaleResolver——直接读请求头 Accept-Language,无需任何配置:
| LocaleResolver | 依据 | 特点 |
|---|---|---|
| AcceptHeaderLocaleResolver | Accept-Language 请求头 | 默认实现,改语言靠浏览器 |
| SessionLocaleResolver | 会话属性 | 用户本次会话可切换语言 |
| CookieLocaleResolver | Cookie | 刷新/重启后仍记住选择 |
可切换语言的经典搭配:SessionLocaleResolver(存储)+ LocaleChangeInterceptor(从 ?lang= 参数改):
@Configuration
public class LocaleConfig {
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver resolver = new SessionLocaleResolver();
resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return resolver;
}
}
// 需要配合拦截器监听切换参数(见第 30 章拦截器注册方式)
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang"); // /hello?lang=en_US 即可切换
registry.addInterceptor(interceptor);
}
}
第四步:在代码里取消息
@RestController
public class HelloApi {
private final MessageSource messageSource;
public HelloApi(MessageSource messageSource) {
this.messageSource = messageSource;
}
@GetMapping("/api/welcome")
public String welcome(@RequestParam String name, Locale locale) {
// Locale 参数由 Spring 自动注入当前请求的语言区域
return messageSource.getMessage("welcome", new Object[]{name}, locale);
}
}
// Accept-Language: zh-CN → 欢迎,张三! Accept-Language: en → Welcome, ZhangSan!
不想要方法参数时,可用 LocaleContextHolder.getLocale() 拿当前线程绑定的 Locale(它在 DispatcherServlet 处理期间总可用)。
视图层的配合
JSP 有 spring:message 标签、Thymeleaf 有 #{...} 表达式,底层都走 MessageSource + 当前 Locale,适合页面文案;接口国际化则直接注入 MessageSource 返回按语言渲染的文案或错误码。
常见坑
- properties 中文乱码:多为编码问题,统一 UTF-8 并核对读取编码。
- 文案找不到:检查 basename 路径与文件位置(classpath 根),或开 fallbackToSystemLocale 观察行为。
- 接口报错文案没生效:错误消息可能来自 Bean Validation 的 ValidationMessages.properties(校验消息与业务消息是两个来源,见第 26 章)。
国际化 = MessageSource(文案仓库)+ LocaleResolver(语言从哪来)+ Locale(当前语言上下文)。默认 AcceptHeaderLocaleResolver 即可起步;要做「站内一键切语言」,就上 Session/Cookie LocaleResolver + LocaleChangeInterceptor 的组合。