页面加载中
博客快捷键
按住 Shift 键查看可用快捷键
ShiftK
开启/关闭快捷键功能
ShiftA
打开/关闭中控台
ShiftD
深色/浅色显示模式
ShiftS
站内搜索
ShiftR
随机访问
ShiftH
返回首页
ShiftL
友链页面
ShiftP
关于本站
ShiftI
原版/本站右键菜单
松开 Shift 键或点击外部区域关闭
互动
最近评论
暂无评论
标签
寻找感兴趣的领域
暂无标签
    0
    文章
    0
    标签
    8
    分类
    10
    评论
    128
    功能
    深色模式
    标签
    JavaScript12TypeScript8React15Next.js6Vue10Node.js7CSS5前端20
    互动
    最近评论
    暂无评论
    标签
    寻找感兴趣的领域
    暂无标签
      0
      文章
      0
      标签
      8
      分类
      10
      评论
      128
      功能
      深色模式
      标签
      JavaScript12TypeScript8React15Next.js6Vue10Node.js7CSS5前端20
      随便逛逛
      博客分类
      文章标签
      复制地址
      深色模式
      AnHeYuAnHeYu
      Search⌘K
      博客
        暂无其他文档

        (六)创建自定义约束

        本文介绍如何在Java中创建自定义Bean Validation约束。首先定义约束注释@CheckCase,使用@Target指定作用目标,@Retention(RUNTIME)确保运行时可用,@Constraint关联验证器,@Repeatable支持重复标注。然后实现ConstraintValidator接口创建验证器CheckCaseValidator,在initialize()中初始化参数,在isValid()中编写大小写校验逻辑。最后可通过payload区分错误严重程度。

        August 26, 20238 分钟 阅读17 次阅读

        有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步,认准https://blog.zysicyj.top

        一、创建一个简单的约束

        1. 约束注释

        • 枚举表示大小写
        public enum CaseMode {
            UPPER,
            LOWER;
        }
        
        • 定义@CheckCase约束
        import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
        import static java.lang.annotation.ElementType.FIELD;
        import static java.lang.annotation.ElementType.METHOD;
        import static java.lang.annotation.ElementType.PARAMETER;
        import static java.lang.annotation.ElementType.TYPE_USE;
        import static java.lang.annotation.RetentionPolicy.RUNTIME;
        @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE })
        @Retention(RUNTIME)
        @Constraint(validatedBy = CheckCaseValidator.class)
        @Documented
        @Repeatable(List.class)
        public @interface CheckCase {
            String message() default "{org.hibernate.validator.referenceguide.chapter06.CheckCase." +
                    "message}";
            Class<?>[] groups() default { };
            Class<? extends Payload>[] payload() default { };
            CaseMode value();
            @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
            @Retention(RUNTIME)
            @Documented
            @interface List {
                CheckCase[] value();
            }
        }
        

        • 测试一下吧
        public class Severity {
            public interface Info extends Payload {
            }
            public interface Error extends Payload {
            }
        }
        
        public class ContactDetails {
            @NotNull(message = "Name is mandatory", payload = Severity.Error.class)
            private String name;
            @NotNull(message = "Phone number not specified, but not mandatory",
                    payload = Severity.Info.class)
            private String phoneNumber;
            // ...
        }
        

        用法

        @Target 定义约束所支持的目标元素类型

        @Retention(RUNTIME):指定此类型的注释将在运行时通过反射方式提供

        @Constraint(validatedBy = CheckCaseValidator.class):将注释类型标记为约束注释,并指定用于验证元素的验证器

        @CheckCase。如果可以在几种数据类型上使用约束,则可以指定几个验证器,每种数据类型一个。

        @Repeatable(List.class):表示注释可以在同一位置重复多次,通常使用不同的配置

        2. 约束验证器

        public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
            private CaseMode caseMode;
            @Override
            public void initialize(CheckCase constraintAnnotation) {
                this.caseMode = constraintAnnotation.value();
            }
            @Override
            public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
                if ( object == null ) {
                    return true;
                }
                if ( caseMode == CaseMode.UPPER ) {
                    return object.equals( object.toUpperCase() );
                }
                else {
                    return object.equals( object.toLowerCase() );
                }
            }
        }
        
        • 自定义错误消息
        public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
            private CaseMode caseMode;
            @Override
            public void initialize(CheckCase constraintAnnotation) {
                this.caseMode = constraintAnnotation.value();
            }
            @Override
            public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
                if ( object == null ) {
                    return true;
                }
                boolean isValid;
                if ( caseMode == CaseMode.UPPER ) {
                    isValid = object.equals( object.toUpperCase() );
                }
                else {
                    isValid = object.equals( object.toLowerCase() );
                }
                if ( !isValid ) {
                    constraintContext.disableDefaultConstraintViolation();
                    constraintContext.buildConstraintViolationWithTemplate(
                            "{org.hibernate.validator.referenceguide.chapter06." +
                            "constraintvalidatorcontext.CheckCase.message}"
                    )
                    .addConstraintViolation();
                }
                return isValid;
            }
        }
        
        • HibernateConstraintValidator(对原版进行扩展)
        public class MyFutureValidator implements HibernateConstraintValidator<MyFuture, Instant> {
            private Clock clock;
            private boolean orPresent;
            @Override
            public void initialize(ConstraintDescriptor<MyFuture> constraintDescriptor,
                    HibernateConstraintValidatorInitializationContext initializationContext) {
                this.orPresent = constraintDescriptor.getAnnotation().orPresent();
                this.clock = initializationContext.getClockProvider().getClock();
            }
            @Override
            public boolean isValid(Instant instant, ConstraintValidatorContext constraintContext) {
                //...
                return false;
            }
        }
        

        将有效负载传递给约束验证器

        - 在ValidatorFactory初始化期间定义约束验证器有效载荷

        ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
                .configure()
                .constraintValidatorPayload( "US" )
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();
        
        • 使用Validator上下文定义约束验证器有效载荷
        HibernateValidatorFactory hibernateValidatorFactory = Validation.byDefaultProvider()
                .configure()
                .buildValidatorFactory()
                .unwrap( HibernateValidatorFactory.class );
        Validator validator = hibernateValidatorFactory.usingContext()
                .constraintValidatorPayload( "US" )
                .getValidator();
        // [...] US specific validation checks
        validator = hibernateValidatorFactory.usingContext()
                .constraintValidatorPayload( "FR" )
                .getValidator();
        // [...] France specific validation checks
        
        • 在约束验证器中使用约束验证器有效载荷
        public class ZipCodeValidator implements ConstraintValidator<ZipCode, String> {
            public String countryCode;
            @Override
            public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
                if ( object == null ) {
                    return true;
                }
                boolean isValid = false;
                String countryCode = constraintContext
                        .unwrap( HibernateConstraintValidatorContext.class )
                        .getConstraintValidatorPayload( String.class );
                if ( "US".equals( countryCode ) ) {
                    // checks specific to the United States
                }
                else if ( "FR".equals( countryCode ) ) {
                    // checks specific to France
                }
                else {
                    // ...
                }
                return isValid;
            }
        }
        

        3. 错误讯息

        org.hibernate.validator.referenceguide.chapter06.CheckCase.message = 案例模式必须为{value}。
        

        4. 使用约束

        public class Car {
            @NotNull
            private String manufacturer;
            @NotNull
            @Size(min = 2, max = 14)
            @CheckCase(CaseMode.UPPER)
            private String licensePlate;
            @Min(2)
            private int seatCount;
            public Car(String manufacturer, String licencePlate, int seatCount) {
                this.manufacturer = manufacturer;
                this.licensePlate = licencePlate;
                this.seatCount = seatCount;
            }
            //getters and setters ...
        }
        
        • 使用约束验证对象
        //invalid license plate
        Car car = new Car( "Morris", "dd-ab-123", 4 );
        Set<ConstraintViolation<Car>> constraintViolations =
                validator.validate( car );
        assertEquals( 1, constraintViolations.size() );
        assertEquals(
                "Case mode must be UPPER.",
                constraintViolations.iterator().next().getMessage()
        );
        //valid license plate
        car = new Car( "Morris", "DD-AB-123", 4 );
        constraintViolations = validator.validate( car );
        assertEquals( 0, constraintViolations.size() );
        

        二、类级别约束

        • 实现一个类级别约束
        @Target({ TYPE, ANNOTATION_TYPE })
        @Retention(RUNTIME)
        @Constraint(validatedBy = { ValidPassengerCountValidator.class })
        @Documented
        public @interface ValidPassengerCount {
            String message() default "{org.hibernate.validator.referenceguide.chapter06.classlevel." +
                    "ValidPassengerCount.message}";
            Class<?>[] groups() default { };
            Class<? extends Payload>[] payload() default { };
        }
        
        public class ValidPassengerCountValidator
                implements ConstraintValidator<ValidPassengerCount, Car> {
            @Override
            public void initialize(ValidPassengerCount constraintAnnotation) {
            }
            @Override
            public boolean isValid(Car car, ConstraintValidatorContext context) {
                if ( car == null ) {
                    return true;
                }
                return car.getPassengers().size() <= car.getSeatCount();
            }
        }
        

        自定义属性路径

        public class ValidPassengerCountValidator
                implements ConstraintValidator<ValidPassengerCount, Car> {
            @Override
            public void initialize(ValidPassengerCount constraintAnnotation) {
            }
            @Override
            public boolean isValid(Car car, ConstraintValidatorContext constraintValidatorContext) {
                if ( car == null ) {
                    return true;
                }
                boolean isValid = car.getPassengers().size() <= car.getSeatCount();
                if ( !isValid ) {
                    constraintValidatorContext.disableDefaultConstraintViolation();
                    constraintValidatorContext
                            .buildConstraintViolationWithTemplate( "{my.custom.template}" )
                            .addPropertyNode( "passengers" ).addConstraintViolation();
                }
                return isValid;
            }
        }
        

        三、交叉级别约束

        • 交叉级别约束
        @Constraint(validatedBy = ConsistentDateParametersValidator.class)
        @Target({ METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
        @Retention(RUNTIME)
        @Documented
        public @interface ConsistentDateParameters {
            String message() default "{org.hibernate.validator.referenceguide.chapter04." +
                    "crossparameter.ConsistentDateParameters.message}";
            Class<?>[] groups() default { };
            Class<? extends Payload>[] payload() default { };
        }
        
        • 通用和交叉级别约束
        @SupportedValidationTarget(ValidationTarget.PARAMETERS)
        public class ConsistentDateParametersValidator implements
                ConstraintValidator<ConsistentDateParameters, Object[]> {
            @Override
            public void initialize(ConsistentDateParameters constraintAnnotation) {
            }
            @Override
            public boolean isValid(Object[] value, ConstraintValidatorContext context) {
                if ( value.length != 2 ) {
                    throw new IllegalArgumentException( "Illegal method signature" );
                }
                //leave null-checking to @NotNull on individual parameters
                if ( value[0] == null || value[1] == null ) {
                    return true;
                }
                if ( !( value[0] instanceof Date ) || !( value[1] instanceof Date ) ) {
                    throw new IllegalArgumentException(
                            "Illegal method signature, expected two " +
                                    "parameters of type Date."
                    );
                }
                return ( (Date) value[0] ).before( (Date) value[1] );
            }
        }
        
        @Constraint(validatedBy = {
                ScriptAssertObjectValidator.class,
                ScriptAssertParametersValidator.class
        })
        @Target({ TYPE, FIELD, PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
        @Retention(RUNTIME)
        @Documented
        public @interface ScriptAssert {
            String message() default "{org.hibernate.validator.referenceguide.chapter04." +
                    "crossparameter.ScriptAssert.message}";
            Class<?>[] groups() default { };
            Class<? extends Payload>[] payload() default { };
            String script();
            ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;
        }
        
        • 指定通用和交叉参数约束的目标
        @ScriptAssert(script = "arg1.size() <= arg0", validationAppliesTo = ConstraintTarget.PARAMETERS)
        public Car buildCar(int seatCount, List<Passenger> passengers) {
            //...
            return null;
        }
        

        四、约束构成

        • 创建一个合成约束 @ValidLicensePlate
        @NotNull
        @Size(min = 2, max = 14)
        @CheckCase(CaseMode.UPPER)
        @Target({ METHOD, FIELD, ANNOTATION_TYPE, TYPE_USE })
        @Retention(RUNTIME)
        @Constraint(validatedBy = { })
        @Documented
        public @interface ValidLicensePlate {
            String message() default "{org.hibernate.validator.referenceguide.chapter06." +
                    "constraintcomposition.ValidLicensePlate.message}";
            Class<?>[] groups() default { };
            Class<? extends Payload>[] payload() default { };
        }
        
        • 组合约束的应用 ValidLicensePlate
        public class Car {
            @ValidLicensePlate
            private String licensePlate;
            //...
        }
        
        • 使用@ReportAsSingleViolation
        @ReportAsSingleViolation
        public @interface ValidLicensePlate {
            String message() default "{org.hibernate.validator.referenceguide.chapter06." +
                    "constraintcomposition.reportassingle.ValidLicensePlate.message}";
            Class<?>[] groups() default { };
            Class<? extends Payload>[] payload() default { };
        }
        ```ayload() default { };
        }
        
        最后更新于 August 26, 2023
        On this page
        暂无目录