Spring Boot带国际化的自定义验证消息实现及问题排查
Spring Boot 验证注解的动态消息与国际化实现问题
问题背景
我正在为验证注解寻找合适的实现方式,现有示例代码如下:
public class UserRequest { @Size(min = 3, max = 50) @NotBlank(message = "name cannot be empty and min 3, max 50 character length") private String name; @Email(message = "email is not valid") private String email; }
需要实现以下特性:
- 像name字段的验证消息一样,能够在消息中动态获取字段名、
min和max等参数值; - 未来可支持国际化。
请问在Spring Boot应用中该如何实现?MessageSource方案是否可行?
更新:尝试后的问题
我尝试MessageSource方案后,得到如下响应,看起来resources目录下的message.properties中的消息并未被解析:
{ "timestamp": "26.02.2023 01:10:15", "status": 400, "error": "Bad Request", "exception": "org.springframework.web.bind.MethodArgumentNotValidException", "message": "Validation failed for object='createPostRequest'. Error count: 1", "errors": [ { "codes": [ "NotBlank.createPostRequest.name", "NotBlank.name", "NotBlank.java.lang.String", "NotBlank" ], "arguments": [ { "codes": [ "createPostRequest.name", "name" ], "arguments": null, "defaultMessage": "name", "code": "name" } ], "defaultMessage": "{name.not-blank}", "objectName": "createPostRequest", "field": "name", "rejectedValue": null, "bindingFailure": false, "code": "NotBlank" } ], "path": "/api/v1/posts" }
解决方案
一、MessageSource方案完全可行
Spring Boot自带的MessageSource结合JSR-380(Bean Validation)的消息插值机制,完全能满足动态获取字段名、验证参数及国际化的需求。
二、完整实现步骤
1. 配置消息资源文件
在resources目录下创建对应语言的消息文件,Spring Boot会自动加载:
- 中文默认消息:
messages.properties - 英文消息:
messages_en.properties
以messages.properties为例,添加验证规则对应的消息模板:
# NotBlank验证:{0}会被替换为字段名 NotBlank={0}不能为空 # Size验证:{0}=字段名,{1}=min值,{2}=max值 Size={0}长度必须在{1}到{2}之间 # Email验证 Email={0}格式不合法
2. 修改实体类的验证注解
不要在注解message属性中写死文本,改为引用消息文件中的key:
public class UserRequest { @Size(min = 3, max = 50, message = "{Size}") @NotBlank(message = "{NotBlank}") private String name; @Email(message = "{Email}") private String email; }
3. 全局异常处理器解析消息
Spring Boot抛出MethodArgumentNotValidException时,需通过MessageSource解析消息参数,返回友好响应:
@RestControllerAdvice public class GlobalExceptionHandler { private final MessageSource messageSource; public GlobalExceptionHandler(MessageSource messageSource) { this.messageSource = messageSource; } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, Object>> handleValidationException(MethodArgumentNotValidException ex, Locale locale) { List<String> errors = ex.getBindingResult() .getFieldErrors() .stream() .map(error -> messageSource.getMessage(error, locale)) .collect(Collectors.toList()); Map<String, Object> response = new HashMap<>(); response.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss"))); response.put("status", HttpStatus.BAD_REQUEST.value()); response.put("error", "参数错误"); response.put("message", "参数验证失败"); response.put("errors", errors); response.put("path", ex.getRequest().getRequestURI()); return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } }
4. 解决消息未解析的问题
从你的响应来看,消息模板未被解析,大概率是以下原因:
- 消息key不匹配:你用了
{name.not-blank},但消息文件中无对应key,或拼写错误; - MessageSource配置错误:如果自定义了MessageSource,需确保
basename设置正确(比如classpath:messages)、默认编码为UTF-8; - 注解message属性错误:实体类中
@NotBlank的message应引用全局key(如"{NotBlank}"),而非自定义的"{name.not-blank}"; - Locale不匹配:确保请求Locale与消息文件对应,或设置默认Locale。
若需自定义MessageSource,参考以下配置:
@Configuration public class MessageConfig { @Bean public MessageSource messageSource() { ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.setBasename("classpath:messages"); messageSource.setDefaultEncoding("UTF-8"); messageSource.setCacheSeconds(3600); // 开发环境可设为0 return messageSource; } @Bean public LocalValidatorFactoryBean validator() { LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean(); bean.setValidationMessageSource(messageSource()); return bean; } }
三、验证效果
当name字段为空时,返回消息:"name不能为空";当name长度不符合要求时,返回"name长度必须在3到50之间",完全满足动态参数和国际化需求。
内容的提问来源于stack exchange,提问作者Jack
相关产品推荐
相关产品推荐

