Bean Validation规范讲解

原创地址:http://www.ibm.com/developerworks/cn/java/j-lo-beanvalid/,谢谢分享学习

Bean Validation

JSR303 规范(Bean Validation 规范)提供了对 JavaEE 和 Java SE 中的 Java Bean 进行验证的方式。该规范主要使用注解的方式来实现对 Java Bean 的验证功能,并且这种方式会覆盖使用 XML 形式的验证描述符,从而使验证逻辑从业务代码中分离出来

 

约束注解

Bean Validation 规范对约束的定义包括两部分,一是约束注解,二是约束验证器,每一个约束注解都存在对应的约束验证器,约束验证器用来验证具体的 Java Bean 是否满足该约束注解声明的条件。JSR303 规范默认提供了几种约束注解的定义,我们也可以扩展规范提供的 API,实现符合自身业务需求的约束注解。

@Null

验证对象是否为空

@NotNull

验证对象是否为非空

@AssertTrue

验证 Boolean 对象是否为 true

@AssertFalse

验证 Boolean 对象是否为 false

@Min

验证 Number  String 对象是否大等于指定的值

@Max

验证 Number  String 对象是否小等于指定的值

@DecimalMin

验证 Number  String 对象是否大等于指定的值,小数存在精度

@DecimalMax

验证 Number  String 对象是否小等于指定的值,小数存在精度

@Size

验证对象(Array,Collection,Map,String)长度是否在给定的范围之内

@Digits

验证 Number  String 的构成是否合法

@Past

验证 Date  Calendar 对象是否在当前时间之前

@Future

验证 Date  Calendar 对象是否在当前时间之后

@Pattern

验证 String 对象是否符合正则表达式的规则

 

约束注解和普通的注解一样,一个典型的约束注解的定义应该至少包括如下内容

@Target({ })   // 约束注解应用的目标元素类型

@Retention()   // 约束注解应用的时机

@Constraint(validatedBy ={})  // 与约束注解关联的验证器

public @interface ConstraintName{

String message()default " ";   // 约束注解验证时的输出消息

Class<?>[]groups() default { };  // 约束注解在验证时所属的组别

Class<?extends Payload>[] payload() default { }; // 约束注解的有效负载

}

约束注解应用的目标元素类型包括 METHOD, FIELD, TYPE, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER。

METHOD 约束相关的 getter 方法;

FIELD 约束相关的属性;

TYPE 约束具体的 Java Bean;

ANNOTATION_TYPE 用在组合约束中;该规范同样也支持对参数(PARAMETER)和构造器(CONSTRUCTOR)的约束。

有效负载通常用来将一些元数据信息与该约束注解相关联,常用的一种情况是用负载表示验证结果的严重程度。

 

一个验证字符串非空的约束注解的定义示例

@Target({ METHOD, FIELD, ANNOTATION_TYPE,CONSTRUCTOR, PARAMETER })

@Retention(RUNTIME)

@Documented

@Constraint(validatedBy ={NotEmptyValidator.class})

public @interface NotEmpty {

String message()default "this string may be empty";

Class<?>[]groups() default { };

Class<?extends Payload>[] payload() default {};

}

 

约束验证器

约束注解定义完成后,需要同时实现与该约束注解关联的验证器。约束验证器的实现需要扩展 JSR303 规范提供的接口 javax.validation.ConstraintValidator

public interface ConstraintValidator<Aextends Annotation, T> {

voidinitialize(A constraintAnnotation);

booleanisValid(T value, ConstraintValidatorContext context);

}

该接口有两个方法,方法 initialize 对验证器进行实例化,它必须在验证器的实例在使用之前被调用,并保证正确初始化验证器,它的参数是约束注解;方法 isValid 是进行约束验证的主体方法,其中 value 参数代表需要验证的实例,context 参数代表约束执行的上下文环境。与该注解对应的验证器的实现。

public class NotEmptyValidator implementsConstraintValidator<NotEmpty, String>{

public voidinitialize(NotEmpty parameters) {

        }

        public boolean isValid(String string,ConstraintValidatorContext constraintValidatorContext) {

                 if (string == null) returnfalse;

                 else if(string.length()<1)return false;

                 else return true;

}

}

 

约束使用

@NotEmpty

private String company;

 

多值约束

多值约束(Multiple Constraints):对于同一个目标元素,在进行约束注解声明时可以同时使用不同的属性达到对该目标元素进行多值验证的目的。

实现多值约束只需要在定义约束注解的同时定义一个 List(@interface List{})。使用该约束注解时,Bean Validation 将 value 数组里面的每一个元素都处理为一个普通的约束注解,并对其进行验证,所有约束条件均符合时才会验证通过。

验证某一字符串是否包含指定的内容示例:

@Target({ METHOD, FIELD, ANNOTATION_TYPE,CONSTRUCTOR, PARAMETER })

@Retention(RUNTIME)

@Documented

@Constraint(validatedBy =PatternOfStringValidator.class)

public @interface PatternOfString {

String mustContainLetter();

        String message() default "thispattern may not be right";

        Class<?>[] groups() default { };

        Class<? extends Payload>[]payload() default {};

@Target({METHOD, FIELD, ANNOTATION_TYPE})

        @Retention(RUNTIME)

@interface List{

                 PatternOfString[] value();

}

}

该约束注解对应的验证器

public class PatternOfStringValidatorimplements ConstraintValidator<PatternOfString, String> {

private StringletterIn;

        public void initialize(PatternOfStringparameters) {

                 this.letterIn=parameters.mustContainLetter();

        }

        public boolean isValid(String string, ConstraintValidatorContextconstraintValidatorContext) {

                 if (string.contains(letterIn))

                           return true;

                 return false;

        }

}

多值约束使用

@PatternOfString.List({

@PatternOfString(mustContainLetter = "CH",

        message = "It does not belong toChina"),

        @PatternOfString(mustContainLetter="MainLand",

        message="It does not belong toMainLand")

})

private String place;

 

组合约束

Bean Validation 规范允许将不同的约束进行组合来创建级别较高且功能较多的约束,从而避免原子级别约束的重复使用

 

验证流程

ValidatorFactory vf =Validation.buildDefaultValidatorFactory();

Validator validator = vf.getValidator();

Set<ConstraintViolation<Employee>>set = validator.validate(JavaBeanInstance);

for (ConstraintViolation<Employee>constraintViolation : set) {

        System.out.println(constraintViolation.getMessage());

}

Bean Validation 规范对 Java Bean的验证流程如下:在实际使用中调用 Validator.validate(JavaBeanInstance) 方法后,Bean Validation 会查找在JavaBeanInstance上所有的约束声明,对每一个约束调用对应的约束验证器进行验证,最后的结果由约束验证器的 isValid 方法产生,如果该方法返回 true,则约束验证成功,否则验证失败。验证失败的约束将产生约束违规对象(ConstraintViolation 的实例)并放到约束违规列表中。验证完成后所有的验证失败信息均能在该列表中查找并输出。

 

前提条件

Bean Validation 规范规定在对 JavaBean 进行约束验证前,目标元素必须满足以下条件:

1、如果验证的是属性(getter方法),那么必须遵从 JavaBean 的命名习惯(JavaBeans 规范)

2、静态的字段和方法不能进行约束验证;

3、约束适用于接口和基类;

4、约束注解定义的目标元素可以是字段、属性或者类型等;

5、可以在类或者接口上使用约束验证,它将对该类或实现该接口的实例进行状态验证;

字段和属性均可以使用约束验证,但是不能将相同的约束重复声明在字段和相关属性(字段的 getter 方法)上

 

级联验证

如果类 A 引用类 B,则在对类 A 的实例进行约束验证时也需要对类 B 的实例进行约束验证,这就是验证的级联性。当对 Java 语言中的集合、数组等类型进行验证时也需要对该类型的每一个元素进行验证

级联验证的方式就是使用@Valid 注解

@Valid

private Person person;

 

验证组

Bean Validation 规范中一个重要的概念,就是组和组序列。组定义了约束的子集。对于一个给定的 Object Graph 结构,有了组的概念,则无需对该 ObjectGraph 中所有的约束进行验证,只需要对该组定义的一个子集进行验证即可。完成组别验证需要在约束声明时进行组别的声明,否则使用默认的组 Default.class.

 

public interface GroupA{}

public class User{

          @NotEmpty (message = "firstname may beempty")

          private String firstname;

 

          @NotEmpty(message = "middlename may beempty", groups = Default.class)

          private String middlename;

          

          @NotEmpty(message = "lastname may beempty",groups = GroupA.class)

          private String lastname;

          

          public static void main(String[] args){

                    User user = new User();

                    ValidatorFactory vf =Validation.buildDefaultValidatorFactory();

                    Validator validator = vf.getValidator();

                    Set<ConstraintViolation<User>> set= validator.validate(user,GroupA.class);

                    for (ConstraintViolation<User>constraintViolation : set) {

                             System.out.println(constraintViolation.getMessage());

                    }

          }

}

在类 User 中需要验证的字段上声明验证时所属的组别属性,如(groups=GroupA.class), 然后在 main 函数中调用 validator.validate(user,GroupA.class)) 方法

验证器只会验证类 User 的 lastname 字段,如果使用 validator.validate(user)),则会使用 Default.class 组别,从而验证firstname 和 middlename 字段。

 

组也有继承的属性。对某一组别进行约束验证的时候,也会对其所继承的基类进行验证。

 

组可以进行隐式定义,其好处是可以不必在约束声明的时候显式声明组别属性,如groups=GroupA.class。

隐式定义的组接口(Animal),其中包含对相应属性(getter 方法)的约束声明。相应的 Java Bean(Dog)实现了该接口

public interface Animal {

@NotEmpty StringgetName();

@NotEmpty StringgetOwnerName();

 }

 public class Dog implements Animal {

        private String name;

        private String ownername;

 

        private String type;

 

public voidsetType(String type) {

                 this.type = type;

        }

        public String getName() {

                 return null;

        }

        public String getOwnerName() {

                 return null;

        }

@NotEmpty(message= "type of the dog may be empty")

        public String getType() {

                 return type;

}

}

这样在对类 Dog 的实例进行验证的时候,如果使用默认的组别(Default.class),则 name,ownername 和 type 都将进行验证;如果使用 Animal 的组别,则只会对 name 和 ownername 属性进行验证

public static void main(String[] args) {

        Dog dog = new Dog();

        ValidatorFactory vf =Validation.buildDefaultValidatorFactory();

        Validator validator = vf.getValidator();

        Set<ConstraintViolation<Dog>>set = validator.validate(dog,Animal.class);

        for (ConstraintViolation<Dog>constraintViolation : set) {

                 System.out.println(constraintViolation.getMessage());

        }

}

 

组序列

默认情况下,不同组别的约束验证是无序的,然而在某些情况下,约束验证的顺序却很重要

如下面两个例子:(1)第二个组中的约束验证依赖于一个稳定状态来运行,而这个稳定状态是由第一个组来进行验证的。(2)某个组的验证比较耗时,CPU 和内存的使用率相对比较大,最优的选择是将其放在最后进行验证。因此,在进行组验证的时候尚需提供一种有序的验证方式,这就提出了组序列的概念

 

一个组可以定义为其他组的序列,使用它进行验证的时候必须符合该序列规定的顺序。在使用组序列验证的时候,如果序列前边的组验证失败,则后面的组将不再给予验证。

 

组序列示例

public interface GroupA{}

public interface GroupB { }

@GroupSequence({Default.class,GroupA.class, GroupB.class})

public interface Group {}

public class User{

         @NotEmpty(message = "firstname may be empty")

         privateString firstname;

         @NotEmpty(message = "middlename may be empty",groups = Default.class)

         privateString middlename;

         @NotEmpty(message= "lastname may be empty", groups = GroupA.class)

         privateString lastname;

         @NotEmpty(message= "country may be empty", groups = GroupB.class)

         privateString country;

         publicstatic void main(String[] args){

                   Useruser = new User();

                   ValidatorFactoryvf = Validation.buildDefaultValidatorFactory();

                   Validatorvalidator = vf.getValidator();

                   Set<ConstraintViolation<User>>set = validator.validate(user,Group.class);

                   for(ConstraintViolation<User> constraintViolation : set){

                            System.out.println(constraintViolation.getMessage());

                   }

         }

}

输出结果为:

middlename may be empty

firstname may be empty

 

由于组序列(Group.class)中前面位置的 Default 组验证失败,后续组别(GroupA,GroupB)不会验证。只有Default 组别的验证通过后,方可进行后续组别(GroupA,GroupB)的验证

 

穿透验证器(TrversableProperty)

穿透验证器主要适用于 JPA 规范,JPA 规范提供一种惰性连接属性,允许实体对象的某些字段被延迟加载,这些被延迟加载的字段需要 JPA 从底层数据库中获取。Bean Validation 规范通过 TraversableResolver 接口来控制这类字段的存取性。在实际使用中需要先调用该接口中的 isReachable() 方法,如果返回 true,则证明该属性是可存取的,方可进行属性的约束验证。同样,在进行级联验证时,也需要首先确定所引用的字段或者属性的可存取性方可进行约束的级联验证。

 

BeanValidation 规范接口及其可扩展的实现

Bean Validation 规范允许用户定制个性化的约束验证,并给出了 4 大类接口供扩展使用。

 

javax.validation.validation   

Bean Validation 规范的 API 默认提供该类,是整个API 的入口,用来产生 Configuraton 对象实例,并启动环境中 ValidationProvider 的具体实现

javax.validation.ValidationProviderResolver

返回执行上下文环境中所有的 BeanValidationProviders 的列表,并对每一个BeanValidationProvider 产生一个对象实例。BeanValidation 规范提供一个默认的实现

javax.validation.spi.ValidationProvider

具体的 BeanValidationProvider 实现需要实现该接口。该接口用来生成具体的Congfiguration 接口的实现

javax.validation.Configuration

收集上下文环境中的配置信息,主要用来计算如何给定正确的 ValidationProvider,并将其委派给 ValidatorFactory 对象

javax.validation.ValidatorFactory

从一个具体的 BeanValidationProvider 中构建 Validator 的实例

 

Validator接口

该接口(javax.validation.Validator)定义了验证实例的方法,主要包括三种

<T>Set<ConstraintViolation<T>> validate(T object, Class<?>...groups)该方法用于验证一个给定的对象

<T>Set<ConstraintViolation<T>>validateProperty(T object,

String propertyName,Class<?>...groups) 该方法用于验证给定对象中的字段或者属性

<T>Set<ConstraintViolation<T>> validateValue(Class<T> beanType,String propertyName, Object value, Class<?>... groups) 该方法用于验证给定对象中的属性的具体值

ValidatorFactory vf =Validation.buildDefaultValidatorFactory();

Validator validator = vf.getValidator();

 

类 Validation 会检索类路径下面所有的 jar 文件,使用 ValidationProviderResolver 接口的默认实现DefaultValidationProviderResolver(BeanValidation 规范提供该类)查找 META-INF/services/目录中的 javax.validation.spi.ValidationProvider 文件 , 在 HibernateValidator4.0 中该文件中声明 org.hibernate.validator.HibernateValidator 类为ValidationProvider 的具体实现

因此 Validation 调用 HibernateValidator 类创建 Configuration 接口的实例,在 Hibernate Validator4.0 中,该实例为ConfigurationImpl。最后由 ConfigurationImpl 类产生 ValidatorFactory 的实例,在 HibernateValidator4.0 中为ValidatorFactoryImpl 类。

如果类路径中存在着多个该规范的实现,这就要用到 Configuration 接口去显示指定要使用的具体实现,然后再产生 ValidatorFactory 的实例

Configuration<HibernateValidatorConfiguration>config =

Validation.byProvider(HibernateValidator.class).configure();

ValidatorFactory vf =config.buildValidatorFactory();

Validator validator = vf.getValidator();

如果想实现符合自身业务逻辑的 BeanValidationProvider 检索规则,只需要实现接口ValidationProviderResolver,而不是仅使用规范提供的默认实现

Configuration<?>config=Validation.byDefaultProvider().providerResolver(

newMyValidationProviderResolver()).configure();

ValidatorFactory vf =config.buildValidatorFactory();

Validator validator = vf.getValidator();

MyValidationProviderResolver 就是自定义的检索规则,负责告诉 BeanValidation 如何在具体环境中进行 BeanValidationProvider 的查找

 

ConstraintViolation接口

该接口(javax.validation.ConstraintViolation)用来描述某一验证的失败信息。对某一个实体对象进行验证的时候,会返回 ConstraintViolation 的集合

Set<ConstraintViolation<Employee>>set = validator.validate(employee);

for (ConstraintViolation<Employee>constraintViolation : set) {

System.out.println(constraintViolation.getMessage());

}

 

MessageInterpolator接口

该接口(javax.validation.MessageInterpolator)用来将验证过程中的失败消息以可读的方式传递给客户端使用者。Bean Validation 规范提供一个默认的消息解析接口,用户可自定义符合自身业务需求的消息解析机制,只需实现该接口即可

Configuration<?> config = Validation.byDefaultProvider().configure();

config.messageInterpolator(new MyMessageInterpolator(config.getDefaultMessageInterpolator()));

其中 MyMessageInterpolator 就是自定义的消息解析器,用来完成特定的逻辑。

Bean Validation 规范的输出消息默认从类路径下的 ValidationMessage.properties文件中读取,用户也可以在约束注解声明的时候使用 message 属性指定消息内容

展开阅读全文

没有更多推荐了,返回首页