自定义校验器未抛出预期错误的排查与修复方案
问题描述
原本通过手动判断做参数校验,当name为null或空时抛出异常,代码如下:
if (myRequestResult.getName() == null || myRequestResult.getName().isEmpty()) { throw exception(); } else { myService.setName(myRequestResult.getName()); }
现在改用自定义Spring Validation校验器,定义了@ValidTestNames注解及其实现类TestNameValidator,将该注解绑定到MyRequestResult的List<String> name字段上,期望传入空数组[]或空字符串""时,抛出自定义错误信息"Name cannot be null or empty"。但实际运行出现以下问题:
- 传入空数组
[]时未触发校验,直接将空数组设置到service中; - 传入空字符串
""时返回400错误,但不符合预期。
相关代码
自定义校验器注解
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = TestNameValidator.class) public @interface ValidTestNames { String message() default "Name cannot be null or empty"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
校验器实现类
public class TestNameValidator implements ConstraintValidator<ValidTestNames, List<String>> { private String message; @Override public void initialize(ValidTestNames constraintAnnotation) { this.message = constraintAnnotation.message(); } @Override public boolean isValid(List<String> ValidTestNames, ConstraintValidatorContext context) { if (ValidTestNames == null || ValidTestNames.isEmpty() || ValidTestNames.stream().anyMatch(String::isBlank)) { System.out.println("Validation failed. Custom error message: " + message); context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate(message) .addConstraintViolation(); return false; } else { List<String> nonBlankNames = ValidTestNames.stream().filter(s -> !s.isBlank()).collect(Collectors.toList()); if (nonBlankNames.isEmpty()) { System.out.println("Validation failed. Custom error message: " + message); context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate(message) .addConstraintViolation(); return false; } } return true; } }
控制器代码
@Log4j2 @RestController(value = "apiMyController") @Validated @RequestMapping(path = "/api/v1/someRandomeURL") @RequiredArgsConstructor(onConstructor = @__({@Autowired})) public class ExecutionResultsController { @RequestMapping(method = RequestMethod.POST) public ExecutionResultDTO create(@RequestBody @Valid MyRequestResult myRequestResult) throws Exception { myService.setName(myRequestResult.getName()); } }
请求实体类字段
@ValidTestNames public List<String> name;
问题排查与修复方案
问题1:空数组[]未触发校验的原因
校验器isValid方法中存在参数名拼写错误:方法参数定义为validTestNames(驼峰小写开头),但判断逻辑里误写为ValidTestNames(大写开头,对应类名),导致空数组的判断条件永远不生效。此外,原代码中else块的重复判断逻辑完全冗余。
问题2:传入空字符串返回400不符合预期的原因
Spring Validation默认会将校验异常转为400状态码响应,如果需要返回自定义格式的错误信息,需全局捕获校验异常并统一处理。
修复后的校验器代码
public class TestNameValidator implements ConstraintValidator<ValidTestNames, List<String>> { private String message; @Override public void initialize(ValidTestNames constraintAnnotation) { this.message = constraintAnnotation.message(); } @Override public boolean isValid(List<String> validTestNames, ConstraintValidatorContext context) { // 处理null、空数组场景 if (validTestNames == null || validTestNames.isEmpty()) { context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate(message) .addConstraintViolation(); return false; } // 处理数组中存在空/空白字符串的场景 boolean hasBlankName = validTestNames.stream().anyMatch(String::isBlank); if (hasBlankName) { context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate(message) .addConstraintViolation(); return false; } return true; } }
额外优化:自定义错误响应格式
添加全局异常处理器,将400响应转为自定义结构:
@RestControllerAdvice public class GlobalValidationExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getAllErrors().forEach(error -> { errors.put("error", error.getDefaultMessage()); }); return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); } @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity<Map<String, String>> handleConstraintViolationExceptions(ConstraintViolationException ex) { Map<String, String> errors = new HashMap<>(); ex.getConstraintViolations().forEach(violation -> { errors.put("error", violation.getMessage()); }); return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); } }
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

