Spring Rest API验证:自定义ProblemDetail错误消息实现问询
解决方案:自定义Validation错误的ProblemDetail响应
针对Spring Boot 3.2.2 + Spring 6.1.3中,HandlerMethodValidationException抛出时ProblemDetail的detail字段固定为“Validation failure”,无法复用验证注解(如@Pattern)中自定义message的问题,提供两种可行方案:
方案一:全局异常处理器(灵活自定义)
通过@RestControllerAdvice捕获验证异常,手动构建包含自定义消息的ProblemDetail:
import org.springframework.http.HttpStatus; import org.springframework.http.ProblemDetail; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.HandlerMethodValidationException; import java.util.HashMap; import java.util.Map; @RestControllerAdvice public class GlobalValidationExceptionHandler { @ExceptionHandler(HandlerMethodValidationException.class) public ProblemDetail handleParamValidationException(HandlerMethodValidationException ex) { ProblemDetail problemDetail = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST); problemDetail.setTitle("参数验证失败"); // 按字段收集所有自定义验证消息 Map<String, String> errorDetails = new HashMap<>(); ex.getAllValidationResults().forEach(result -> { result.getResolvableErrors().forEach(error -> { if (error instanceof FieldError fieldError) { errorDetails.put(fieldError.getField(), fieldError.getDefaultMessage()); } else { errorDetails.put(error.getObjectName(), error.getDefaultMessage()); } }); }); // 将自定义消息存入ProblemDetail的扩展属性 problemDetail.setProperty("errors", errorDetails); // 可选:将detail设置为具体错误描述(比如第一个错误消息) // problemDetail.setDetail(errorDetails.values().stream().findFirst().orElse("参数格式不符合要求")); return problemDetail; } // 补充处理@Valid注解在@RequestBody时抛出的MethodArgumentNotValidException @ExceptionHandler(org.springframework.web.bind.MethodArgumentNotValidException.class) public ProblemDetail handleRequestBodyValidationException(org.springframework.web.bind.MethodArgumentNotValidException ex) { ProblemDetail problemDetail = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST); problemDetail.setTitle("请求体参数验证失败"); Map<String, String> errorDetails = new HashMap<>(); ex.getBindingResult().getFieldErrors().forEach(error -> { errorDetails.put(error.getField(), error.getDefaultMessage()); }); problemDetail.setProperty("errors", errorDetails); return problemDetail; } }
方案二:使用Spring Boot扩展点(贴合框架自动配置)
利用Spring Boot提供的ValidationProblemDetailCustomizer接口,统一自定义所有验证异常的ProblemDetail:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.ProblemDetail; import org.springframework.validation.FieldError; import org.springframework.web.method.annotation.HandlerMethodValidationException; import org.springframework.web.servlet.mvc.method.annotation.ValidationProblemDetailCustomizer; import java.util.HashMap; import java.util.Map; @Configuration public class ValidationProblemConfig { @Bean public ValidationProblemDetailCustomizer validationProblemDetailCustomizer() { return problemDetail -> { // 处理HandlerMethodValidationException(@Valid/@Validated在方法参数上的验证) if (problemDetail.getCause() instanceof HandlerMethodValidationException ex) { Map<String, String> errorDetails = new HashMap<>(); ex.getAllValidationResults().forEach(result -> { result.getResolvableErrors().forEach(error -> { if (error instanceof FieldError fieldError) { errorDetails.put(fieldError.getField(), fieldError.getDefaultMessage()); } else { errorDetails.put(error.getObjectName(), error.getDefaultMessage()); } }); }); problemDetail.setTitle("参数验证失败"); problemDetail.setDetail("请检查以下参数错误"); problemDetail.setProperty("errors", errorDetails); } // 补充处理MethodArgumentNotValidException(@Valid在@RequestBody上的验证) else if (problemDetail.getCause() instanceof org.springframework.web.bind.MethodArgumentNotValidException ex) { Map<String, String> errorDetails = new HashMap<>(); ex.getBindingResult().getFieldErrors().forEach(error -> { errorDetails.put(error.getField(), error.getDefaultMessage()); }); problemDetail.setTitle("请求体参数验证失败"); problemDetail.setDetail("请检查请求体参数格式"); problemDetail.setProperty("errors", errorDetails); } }; } }
效果说明
两种方案都能实现:
- 替换默认的
detail固定文本为自定义提示 - 将验证注解(如
@Pattern(message="用户名只能为小写字母"))中的message内容提取到ProblemDetail的errors属性中,便于前端精准展示错误 - 无需依赖消息码配置,直接复用注解上的自定义消息
内容的提问来源于stack exchange,提问作者ZeroCyan
相关产品推荐
相关产品推荐

