Bean Validator校验同类型嵌套对象时如何为错误消息加前缀
Bean Validation 嵌套校验同类型Bean错误消息重复解决方案
Bean Validation 规范本身没有提供和@Valid完全等价、支持给嵌套校验错误加统一前缀的内置注解,可通过以下两种方案实现需求:
方案1:自定义带前缀配置的嵌套校验注解(通用方案,不依赖框架)
自定义@ValidWithPrefix注解替代原生@Valid,注解内置消息前缀配置项,校验时自动给当前嵌套对象下的所有校验错误拼接前缀。
- 定义注解:
import jakarta.validation.Constraint; import jakarta.validation.Payload; import java.lang.annotation.*; @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = ValidWithPrefixValidator.class) public @interface ValidWithPrefix { String message() default ""; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; // 配置当前嵌套Bean所有校验错误的统一前缀 String messagePrefix(); }
- 编写对应校验逻辑实现:
import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validation; import jakarta.validation.Validator; import java.util.Set; public class ValidWithPrefixValidator implements ConstraintValidator<ValidWithPrefix, Object> { private String messagePrefix; @Override public void initialize(ValidWithPrefix constraintAnnotation) { this.messagePrefix = constraintAnnotation.messagePrefix(); } @Override public boolean isValid(Object nestedBean, ConstraintValidatorContext context) { // 嵌套对象为空时不触发校验,和@Valid默认行为一致,空校验请单独配置@NotNull if (nestedBean == null) { return true; } Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); Set<ConstraintViolation<Object>> violations = validator.validate(nestedBean); if (violations.isEmpty()) { return true; } // 禁用默认生成的无前缀错误 context.disableDefaultConstraintViolation(); // 遍历所有嵌套校验错误,拼接前缀后注册到校验上下文 for (ConstraintViolation<Object> violation : violations) { context.buildConstraintViolationWithTemplate(messagePrefix + violation.getMessage()) .addPropertyNode(violation.getPropertyPath().toString()) .addConstraintViolation(); } return false; } }
- 替换原有
@Valid注解使用即可:
class B { @ValidWithPrefix(messagePrefix = "Original person: ") A origin; @ValidWithPrefix(messagePrefix = "Destination person: ") A destination; }
配置后origin下name为空时返回错误为Original person: Name is obligatory.,destination下同字段错误为Destination person: Name is obligatory.,完全符合需求。
方案2:全局异常拦截层统一拼接前缀(适合Spring等带全局异常处理的Web框架)
如果不想自定义校验注解,可以在全局校验异常捕获逻辑中处理:
- 提前维护嵌套属性名和对应错误前缀的映射关系,比如
origin对应Original person:、destination对应Destination person: - 捕获校验异常后,遍历所有
ConstraintViolation,解析错误对应的属性路径,匹配到对应前缀后拼接在原始错误消息前,再返回给前端 - 该方案无需修改原有Bean上的
@Valid注解,但属性名重构时需要同步更新映射配置,维护成本略高。
注:提供的复现代码中A类定义了两个同名的
name属性,第二个@NotBlank("Address is obligatory.")对应的字段应为address,属于笔误,实际开发注意修正即可。
内容的提问来源于stack exchange,提问作者Haroldo_OK
相关产品推荐
相关产品推荐

