You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义校验器未抛出预期错误的排查与修复方案

问题描述

原本通过手动判断做参数校验,当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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 00:20:55