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
相关产品推荐
相关产品推荐

