springboot 之 i18n国际化 二(参数校验提示国际化)
在 Java(尤其是 Spring Boot)中,注解校验 + i18n 国际化 是实现多语言友好错误提示的关键技术。它允许你使用 @NotNull、@NotBlank 等 Bean Validation 注解,并根据用户语言环境返回对应语言的错误信息。
下面从 原理、配置步骤、完整示例 到 高级技巧 详细说明。
一、核心原理
1. 技术栈组合
- Bean Validation (JSR-380):如 Hibernate Validator(Spring Boot 默认集成)
- 国际化(i18n):通过
MessageSource加载不同语言的错误消息文件 - Spring MVC:自动将校验失败结果绑定到
BindingResult,并支持国际化错误码解析
2. 工作流程
- 用户提交表单/JSON 请求
- Controller 方法参数标注
@Valid - Spring 调用 Hibernate Validator 执行校验
- 校验失败时,生成
ConstraintViolation,其 默认错误码 如:javax.validation.constraints.NotNull.messageorg.hibernate.validator.constraints.Length.message
- Spring 从
MessageSource中查找该错误码对应的 国际化消息 - 返回对应语言的错误提示(如中文“姓名不能为空”)
✅ 关键:自定义
ValidationMessages.properties文件覆盖默认英文提示
二、完整实现步骤(Spring Boot 示例)
步骤 1:添加依赖(通常已包含)
<!-- Spring Boot Web 自动引入 Hibernate Validator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
步骤 2:创建国际化校验消息文件
在 src/main/resources 下创建:
resources/
└── i18n/
├── ValidationMessages.properties # 默认(兜底)
├── ValidationMessages_zh_CN.properties # 简体中文
└── ValidationMessages_en_US.properties # 英文
⚠️ 必须命名为
ValidationMessages.properties!这是 Bean Validation 规范约定的默认 basename。
文件内容示例:
# ValidationMessages.properties(默认)
javax.validation.constraints.NotBlank.message=must not be blank
javax.validation.constraints.Size.message=size must be between {min} and {max}
# ValidationMessages_zh_CN.properties
javax.validation.constraints.NotBlank.message=不能为空
javax.validation.constraints.Size.message=长度必须在{min}到{max}之间
org.hibernate.validator.constraints.Length.message=长度必须为{min}到{max}个字符
# 自定义错误码(推荐)
user.name.size=用户名长度必须为2-20个字符
user.email.format=邮箱格式不正确
💡 占位符说明:
{min},{max}:来自注解属性(如@Size(min=2, max=20))- 你也可以在自定义消息中使用这些变量
步骤 3:配置 MessageSource(可选,但推荐)
虽然 ValidationMessages 会被自动加载,但建议显式配置以统一管理:
// config/I18nConfig.java
@Configuration
public class I18nConfig {
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasename("i18n/ValidationMessages"); // 注意:不带 .properties
source.setDefaultEncoding("UTF-8");
source.setFallbackToSystemLocale(false);
return source;
}
// 同时配置 LocaleResolver(用于切换语言)
@Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return resolver;
}
}
✅ 如果你已有全局
messages.properties,不要混淆:
ValidationMessages.properties→ 仅用于校验错误messages.properties→ 用于@Value("#{...}")或手动messageSource.getMessage()
步骤 4:定义 DTO 并使用校验注解
import javax.validation.constraints.*;
public class UserDTO {
@NotBlank(message = "{user.name.size}") // 使用自定义错误码
@Size(min = 2, max = 20)
private String name;
@Email(message = "{user.email.format}")
private String email;
@Min(value = 18, message = "年龄不能小于{value}岁") // 直接写消息(不推荐多语言)
private Integer age;
// getter/setter...
}
✅ 最佳实践:
- 使用
{xxx}引用ValidationMessages中的 key,而非硬编码消息- 这样才能实现真正的国际化
步骤 5:Controller 中处理校验结果
@RestController
public class UserController {
@PostMapping("/users")
public ResponseEntity<?> createUser(
@Valid @RequestBody UserDTO user,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
List<String> errors = bindingResult.getFieldErrors().stream()
.map(FieldError::getDefaultMessage) // 自动国际化!
.collect(Collectors.toList());
return ResponseEntity.badRequest().body(errors);
}
// 保存用户...
return ResponseEntity.ok("Success");
}
}
🔍 关键点:
FieldError.getDefaultMessage()会自动调用MessageSource,根据当前Locale返回对应语言的消息!
步骤 6:测试多语言效果
请求 1:Accept-Language: zh-CN
POST /users
Content-Type: application/json
Accept-Language: zh-CN
{"name": "", "email": "invalid", "age": 10}
响应:
[
"不能为空",
"邮箱格式不正确",
"年龄不能小于18岁"
]
请求 2:Accept-Language: en-US
Accept-Language: en-US
响应:
[
"must not be blank",
"not a well-formed email address",
"年龄不能小于18岁" // ← 注意:这条是硬编码,不会翻译!
]
⚠️ 再次强调:只有通过
{key}引用的消息才会国际化!
三、高级技巧
1. 自定义校验注解 + 国际化
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface ValidPhoneNumber {
String message() default "{user.phone.invalid}"; // 国际化 key
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// ValidationMessages_zh_CN.properties
user.phone.invalid=手机号格式不正确
2. 动态参数传递(如字段名)
# ValidationMessages_zh_CN.properties
user.field.required={0} 不能为空
@NotBlank(message = "{user.field.required}")
private String name;
❌ 但注意:Hibernate Validator 不支持
{0}自动替换字段名!
✅ 解决方案:使用@FieldMatch等自定义注解,或在 Controller 中手动拼接。
3. 全局异常处理(推荐方式)
避免每个 Controller 写 BindingResult:
@RestControllerAdvice
public class ValidationExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<List<String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
return ResponseEntity.badRequest().body(
ex.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.toList())
);
}
}
四、常见问题排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 错误消息仍是英文 | 未创建 ValidationMessages_zh_CN.properties |
检查文件名、路径、编码 |
| 中文乱码 | 文件非 UTF-8 编码 | IDE 中设置 .properties 为 UTF-8,并启用 ASCII 转换 |
| 自定义消息未生效 | message = "xxx" 而非 message = "{key}" |
改为引用资源文件中的 key |
| 语言未切换 | 未配置 LocaleResolver |
添加 AcceptHeaderLocaleResolver 或参数切换 |
总结
✅ 实现要点:
- 创建
ValidationMessages_{lang}.properties文件 - 注解中使用
message = "{your.key}"引用 key - 确保
MessageSource能加载到这些文件 - 通过
Accept-Language或 URL 参数切换语言
本文来自博客园,作者:蓝迷梦,转载请注明原文链接:https://www.cnblogs.com/hewei-blogs/articles/19511622

浙公网安备 33010602011771号