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

Spring Boot 3中如何通过自定义注解在验证失败时抛出自定义异常

自定义验证注解的性能优化问题

1. 我的实现目标

我希望创建一个支持errorCode和errorMessage参数的自定义注解。

示例使用方式:

@NotBlank(message = "字段X不能为空。", errorCode = 23442)

因此我定义了如下结构的NotBlank注解:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD, PARAMETER})
@Constraint(validatedBy = {NotBlankAnnotationProcessor.class})
public @interface NotBlank {
  String message() default "{commons.validation.constraints.NotBlank.message}";

  @SuppressWarnings("unused")
  Class<?>[] groups() default {};

  @SuppressWarnings("unused")
  Class<? extends Payload>[] payload() default {};

  long errorCode() default 400;
}

同时编写了对应的验证处理器NotBlankAnnotationProcessor:

@Getter
public class NotBlankAnnotationProcessor implements ConstraintValidator<NotBlank, String> {
  private String message;
  private long errorCode;

  @Override
  public void initialize(NotBlank annotation) {
    message = annotation.message();
    errorCode = annotation.errorCode();
  }

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    boolean isValid = !Strings.isBlank(value);

    if (isValid) {
      return true;
    }
    context.disableDefaultConstraintViolation();
    var serializedJSON = getSerializedObject(getMessage(), getErrorCode()); // 序列化
    context.buildConstraintViolationWithTemplate(serializedJSON).addConstraintViolation();
    return false;
  }
}

在var serializedJSON = getSerializedObject(getMessage(), getErrorCode());这一行中,由于无法直接抛出带自定义内容的异常,我将message和errorCode序列化为JSON负载,然后在exceptionHandler中反序列化该JSON字符串:

@ExceptionHandler
ResponseEntity<Map<String, Object>> handleMethodArgumentException(
    MethodArgumentNotValidException exception) {
  Map<String, Object> body = new LinkedHashMap<>(1, 1);

  final List<ErrorStructure> errorStructures =
      exception.getBindingResult().getFieldErrors().stream()
          .map(error -> deserialize(error.getDefaultMessage()))
          .toList();
  body.put("messages", errorStructures);
  return ResponseEntity.status(BAD_REQUEST).contentType(APPLICATION_JSON).body(body);
}

以下是我用于序列化和反序列化的工具类:

public final class AnnotationProcessorToExceptionHandlerAdapter {

  private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

  // 不可实例化的工具类
  private AnnotationProcessorToExceptionHandlerAdapter() {
    throw new AssertionError("禁止实例化!");
  }

  @SneakyThrows
  public static String getSerializedObject(final String errorMessage, final long errorCode) {
    ErrorStructure errorStructure = new ErrorStructure(errorMessage, errorCode);
    return OBJECT_MAPPER.writeValueAsString(errorStructure);
  }

  @SneakyThrows
  public static ErrorStructure deserialize(final String serializedValue) {
    return OBJECT_MAPPER.readValue(serializedValue, ErrorStructure.class);
  }
}

2. 存在的问题

当前方案能正常工作,但我认为在高TPS(每秒事务数)场景下,不必要的序列化和反序列化会引发性能问题。

3. 我不想采用的实现方式

我不想在现有代码库的数百个方法参数中添加BindingResults并逐个检查错误,例如:

void onSomething(@RequestBody @Validated WithdrawMoney withdrawal, BindingResult errors) {
  if (errors.hasErrors()) {
           throw new ValidationException(errors);
  }
}

4. 已参考的文章

我已阅读以下Stack Overflow解答:

  • 自定义注解能否抛出自定义异常而非MethodArgumentNotValidException?
  • 如何让自定义注解抛出自定义错误而非默认消息?
  • 使用@javax.validation.Valid时,如何正确抛出自定义异常?
  • Spring Boot如何返回自定义的验证约束错误消息?
  • Spring Boot中如何实现带国际化的自定义验证消息?

恳请各位提供帮助!
感谢!

内容的提问来源于stack exchange,提问作者Zahid Khan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 20:43:14