如何在javax.validation组合注解中传递kind参数并让ConstraintValidator访问?
如何创建带参数传递的组合验证注解
当然可以实现这种组合注解的需求!我来一步步给你拆解怎么搞定这个问题——包括如何传递kind参数,以及让对应的ConstraintValidator能正确访问到这个参数。
方案一:纯JSR-380通用实现(不依赖Spring)
这种方式适用于任何JSR-380(Bean Validation)兼容的环境,不需要额外框架支持。
步骤1:定义组合注解@ValidReusableType
首先我们要创建自己的组合注解,它会包含需要的参数,并指定对应的验证器:
import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; import static javax.validation.ConstraintComposition.AND; @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @ConstraintComposition(AND) // 表示所有内部约束都要满足 @Constraint(validatedBy = ValidReusableTypeValidator.class) public @interface ValidReusableType { // 我们需要传递的kind参数 String kind(); // 标准验证注解必备属性 String message() default "Invalid SomeReusableType instance for kind: {kind}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
步骤2:实现组合注解的验证器
接下来编写ValidReusableTypeValidator,它的核心职责是:
- 获取当前注解的
kind参数 - 初始化并调用原来三个
@ValidProperty注解的验证器,传递kind参数 - 统一处理验证结果和错误消息
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; public class ValidReusableTypeValidator implements ConstraintValidator<ValidReusableType, SomeReusableType> { private String targetKind; private ValidPropertyAValidator propertyAValidator; private ValidPropertyBValidator propertyBValidator; private ValidPropertyCValidator propertyCValidator; @Override public void initialize(ValidReusableType constraintAnnotation) { this.targetKind = constraintAnnotation.kind(); // 初始化三个子验证器,并为它们设置对应的kind参数 this.propertyAValidator = new ValidPropertyAValidator(); this.propertyBValidator = new ValidPropertyBValidator(); this.propertyCValidator = new ValidPropertyCValidator(); // 动态创建带有指定kind的@ValidPropertyA注解实例(纯JSR-380方式) ValidPropertyA customPropertyA = createCustomAnnotation(ValidPropertyA.class, "kind", targetKind); propertyAValidator.initialize(customPropertyA); // 同理处理B和C ValidPropertyB customPropertyB = createCustomAnnotation(ValidPropertyB.class, "kind", targetKind); propertyBValidator.initialize(customPropertyB); ValidPropertyC customPropertyC = createCustomAnnotation(ValidPropertyC.class, "kind", targetKind); propertyCValidator.initialize(customPropertyC); } @Override public boolean isValid(SomeReusableType value, ConstraintValidatorContext context) { if (value == null) { return true; // 根据业务需求决定是否允许null } // 禁用默认错误消息,避免重复输出 context.disableDefaultConstraintViolation(); boolean isValid = true; // 调用子验证器并收集结果 if (!propertyAValidator.isValid(value, context)) { isValid = false; context.buildConstraintViolationWithTemplate("Invalid Property A for kind: " + targetKind) .addConstraintViolation(); } if (!propertyBValidator.isValid(value, context)) { isValid = false; context.buildConstraintViolationWithTemplate("Invalid Property B for kind: " + targetKind) .addConstraintViolation(); } if (!propertyCValidator.isValid(value, context)) { isValid = false; context.buildConstraintViolationWithTemplate("Invalid Property C for kind: " + targetKind) .addConstraintViolation(); } return isValid; } // 工具方法:动态创建带有指定属性的注解实例 private <T extends Annotation> T createCustomAnnotation(Class<T> annotationClass, String propertyName, String propertyValue) { Map<String, Object> properties = new HashMap<>(); properties.put(propertyName, propertyValue); return (T) Proxy.newProxyInstance( annotationClass.getClassLoader(), new Class[]{annotationClass}, (proxy, method, args) -> { String methodName = method.getName(); if (methodName.equals("annotationType")) { return annotationClass; } return properties.getOrDefault(methodName, method.getDefaultValue()); } ); } }
方案二:Spring环境下的简化实现
如果你用的是Spring框架,可以利用Spring的AnnotationUtils来简化注解参数的传递,不需要手动写动态代理:
步骤1:定义带@AliasFor的组合注解
import org.springframework.core.annotation.AliasFor; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; import static javax.validation.ConstraintComposition.AND; @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @ConstraintComposition(AND) @Constraint(validatedBy = ValidReusableTypeValidator.class) @ValidPropertyA @ValidPropertyB @ValidPropertyC public @interface ValidReusableType { // 用@AliasFor关联三个子注解的kind属性 @AliasFor(annotation = ValidPropertyA.class, attribute = "kind") @AliasFor(annotation = ValidPropertyB.class, attribute = "kind") @AliasFor(annotation = ValidPropertyC.class, attribute = "kind") String kind(); String message() default "Invalid SomeReusableType instance for kind: {kind}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
步骤2:简化验证器实现
Spring会自动帮我们把@ValidReusableType的kind参数传递给三个子注解,所以验证器可以直接复用原来的逻辑,甚至不需要自定义验证器(如果子注解的验证器已经能正常工作的话)。如果需要统一处理,可以参考方案一的验证器逻辑,但不需要手动创建注解实例。
最终使用方式
不管用哪种方案,你都可以按照期望的方式使用组合注解:
@Valid @ValidReusableType(kind = "standard") private SomeReusableType standardType; @Valid @ValidReusableType(kind = "large") private SomeReusableType largeType;
这里的@Valid用来触发嵌套验证(如果SomeReusableType内部还有需要验证的属性),@ValidReusableType负责处理你原来的三个属性验证逻辑,并传递kind参数。
内容的提问来源于stack exchange,提问作者Rcunn87
相关产品推荐
相关产品推荐

