一.概述
1.在项目开发工程中,后台在很多场景都需要进行校验操作,比如:前台表单提交到后台,系统接口调用,数据传输等等。而且很多项目采用MVC分层式设计,每层还有需要进行相应地校验,这样在项目较大,多人协作开发的时候,会造成大量重复校验代码,且出错率高。
2.最好是将验证逻辑与相应的域模型进行绑定,这样方便做校验管理。
3.什么是Bean Validation?
- Bean Validation 为JavaBean验证定义了相应的元数据模型和API。缺省的元数据是java Annotations,通过使用XML可以对原有的元数据信息进行覆盖和扩展。Bean Validation是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。
- Hibernate Validator是Bean Validation的参考实现,Bean Validation提供了JSR 303规范中所有内置Constraint的实现,除此之外还有一些附件的Constraint。
二.helloWord
1.依赖
spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖。
但是hibernate-validator需要el表达式
因此要在pom中引入:
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b08</version>
</dependency>
2.约束介绍
3.代码
package com.miracle.demo3.a_helloworld;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Iterator;
import java.util.Set;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
public class Car {
@NotNull(message = "车辆制造商不能为空")
private String manufacturer;
@NotNull
@Size(min = 2, max = 14)
private String licensePlate;
@Min(2)
private int seatCount;
public Car(String manufacturer, String licensePlate, int seatCount) {
this.manufacturer = manufacturer;
this.licensePlate = licensePlate;
this.seatCount = seatCount;
}
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public String getLicensePlate() {
return licensePlate;
}
public void setLicensePlate(String licensePlate) {
this.licensePlate = licensePlate;
}
public int getSeatCount() {
return seatCount;
}
public void setSeatCount(int seatCount) {
this.seatCount = seatCount;
}
public static void main(String[] args) {
// 创建校验工厂
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
// 获取校验对象
Validator validator = factory.getValidator();
// 使用校验对象,校验对象模型(Domain Model)
Car car = new Car(null, null, 1);
Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car);
Iterator<ConstraintViolation<Car>> iterator = constraintViolations.iterator();
System.out.println("有" + constraintViolations.size() + "条校验出错的信息");
while (iterator.hasNext()){
ConstraintViolation<Car> next = iterator.next();
System.out.println("校验出现错误,字段是:" + next.getPropertyPath() + ",错误的信息是:" + next.getMessage());
}
}
}
有时只想校验部分字段
可以设置校验组,选择对应组进行校验
- 新建任意一个接口CarChecks.java
package com.miracle.demo3.a_helloworld;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
public interface CarChecks {
}
- 在约束注解上标记组
package com.miracle.demo3.a_helloworld;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Iterator;
import java.util.Set;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
public class Car {
/**
* groups = CarChecks.class:用来表示一个组,
* 校验的时候可以针对标注组的CarChecks.class标签来判断校验哪些字段
*/
@NotNull(message = "车辆制造商不能为空" , groups = CarChecks.class)
private String manufacturer;
@NotNull(groups = CarChecks.class)
@Size(min = 2, max = 14)
private String licensePlate;
@Min(2)
private int seatCount;
public Car(String manufacturer, String licensePlate, int seatCount) {
this.manufacturer = manufacturer;
this.licensePlate = licensePlate;
this.seatCount = seatCount;
}
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public String getLicensePlate() {
return licensePlate;
}
public void setLicensePlate(String licensePlate) {
this.licensePlate = licensePlate;
}
public int getSeatCount() {
return seatCount;
}
public void setSeatCount(int seatCount) {
this.seatCount = seatCount;
}
public static void main(String[] args) {
// 创建校验工厂
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
// 获取校验对象
Validator validator = factory.getValidator();
// 使用校验对象,校验对象模型(Domain Model)
Car car = new Car(null, null, 1);
/**
* car : 要校验哪个对象
* CarChecks.class : 要校验的组
*/
Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car, CarChecks.class);
Iterator<ConstraintViolation<Car>> iterator = constraintViolations.iterator();
System.out.println("有" + constraintViolations.size() + "条校验出错的信息");
while (iterator.hasNext()){
ConstraintViolation<Car> next = iterator.next();
System.out.println("校验出现错误,字段是:" + next.getPropertyPath() + ",错误的信息是:" + next.getMessage());
}
}
}
三.声明和验证bean
分成3种类型的bean约束:
- 字段级别约束
- 属性级别约束
- 类级别约束
1.字段级别(field level)约束
当约束被定义在字段上的时候,这个字段的值是通过字段访问策略来获取并验证的。也就是说,Bean Validation的实现者会直接访问这个实例变量而不会调用属性的访问器(getter),即使这个方法存在。
package com.miracle.demo3.b_bean;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
import java.util.Iterator;
import java.util.Set;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
public class Car1 {
@NotNull
private String menufacturer;
@AssertTrue
private boolean isRegistered;
public Car1(@NotNull String menufacturer, @AssertTrue boolean isRegistered) {
this.menufacturer = menufacturer;
this.isRegistered = isRegistered;
}
public static void main(String[] args) {
Car1 car1 = new Car1(null, false);
// 创建校验工厂
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
// 获取校验对象
Validator validator = factory.getValidator();
// 使用校验对象,校验对象模型(Domain Model)
Set<ConstraintViolation<Car1>> constraintViolations = validator.validate(car1);
Iterator<ConstraintViolation<Car1>> iterator = constraintViolations.iterator();
System.out.println("有" + constraintViolations.size() + "条校验出错的信息");
while (iterator.hasNext()){
ConstraintViolation<Car1> next = iterator.next();
System.out.println("校验出现错误,字段是:" + next.getPropertyPath() + ",错误的信息是:" + next.getMessage());
}
}
}
2.属性约束
即,把约束标注在get方法上
package com.miracle.demo3.b_bean;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
import java.util.Iterator;
import java.util.Set;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
public class Car2 {
private String menufacturer;
private boolean isRegistered;
public Car2(String menufacturer, boolean isRegistered) {
this.menufacturer = menufacturer;
this.isRegistered = isRegistered;
}
@NotNull
public String getMenufacturer() {
return menufacturer;
}
public void setMenufacturer(String menufacturer) {
this.menufacturer = menufacturer;
}
@AssertTrue
public boolean isRegistered() {
return isRegistered;
}
public void setRegistered(boolean registered) {
isRegistered = registered;
}
public static void main(String[] args) {
Car2 car1 = new Car2(null, false);
// 创建校验工厂
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
// 获取校验对象
Validator validator = factory.getValidator();
// 使用校验对象,校验对象模型(Domain Model)
Set<ConstraintViolation<Car2>> constraintViolations = validator.validate(car1);
Iterator<ConstraintViolation<Car2>> iterator = constraintViolations.iterator();
System.out.println("有" + constraintViolations.size() + "条校验出错的信息");
while (iterator.hasNext()){
ConstraintViolation<Car2> next = iterator.next();
System.out.println("校验出现错误,字段是:" + next.getPropertyPath() + ",错误的信息是:" + next.getMessage());
}
}
}
3.类级别约束
一个约束也能够被放在类级别上。当一个约束被标注在一个类上的时候,这个类的实例对象被传递给ConstraintValidator。当需要同时校验多个属性来验证一个对象或者一个属性在验证的时候,需要另外的属性的信息的时候,类级别的约束会很有用。
要验证类集合字段中,集合的数量不能大于5
package com.miracle.demo3.b_bean;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.miracle.demo3.b_bean;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.PACKAGE,ElementType.FIELD,ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PassagerCountValidator.class)
@Documented
public @interface PassagerCount {
String message() default "乘客数量不能大于5";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int value();
}
package com.miracle.demo3.b_bean;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.List;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
// 由于注解放到类上,ConstraintValidator第二个泛型为Object
public class PassagerCountValidator implements ConstraintValidator<PassagerCount, Object> {
private String message;
private int count;
@Override
public void initialize(PassagerCount constraintAnnotation) {
this.message = constraintAnnotation.message();
this.count = constraintAnnotation.value();
}
@Override
public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {
constraintValidatorContext.disableDefaultConstraintViolation();
constraintValidatorContext.buildConstraintViolationWithTemplate(message).addPropertyNode("").addConstraintViolation();
/*
object 为 Set<ConstraintViolation<Car3>> constraintViolations = validator.validate(car3)
中的 car3 对象
*/
Car3 car3 = (Car3) object;
List<Person> list = car3.getList();
if (list != null && list.size() <= count){
return true;
}
return false;
}
}
测试类
package com.miracle.demo3.b_bean;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
/*
使用类的约束,完成,乘客数量不能大于5
*/
@PassagerCount(value = 5, message = "aaa")
public class Car3 {
@NotNull
private String manufacturer;
@NotNull
@Size(min = 2, max = 14)
private String licensePlate;
@Min(2)
private int seatCount;
public List<Person> getList() {
return list;
}
public void setList(List<Person> list) {
this.list = list;
}
// 定义乘客的数量,集合存放
private List<Person> list;
public Car3(String manufacturer, String licensePlate, int seatCount) {
this.manufacturer = manufacturer;
this.licensePlate = licensePlate;
this.seatCount = seatCount;
}
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public String getLicensePlate() {
return licensePlate;
}
public void setLicensePlate(String licensePlate) {
this.licensePlate = licensePlate;
}
public int getSeatCount() {
return seatCount;
}
public void setSeatCount(int seatCount) {
this.seatCount = seatCount;
}
public static void main(String[] args) {
Car3 car3 = new Car3("一汽大众", "京B-12325", 5);
List<Person> list = new ArrayList<>();
list.add(new Person());
list.add(new Person());
list.add(new Person());
list.add(new Person());
list.add(new Person());
list.add(new Person());
car3.setList(list);
// 创建校验工厂
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
// 获取校验对象
Validator validator = factory.getValidator();
Set<ConstraintViolation<Car3>> constraintViolations = validator.validate(car3);
Iterator<ConstraintViolation<Car3>> iterator = constraintViolations.iterator();
System.out.println("有" + constraintViolations.size() + "条校验出错的信息");
while (iterator.hasNext()){
ConstraintViolation<Car3> next = iterator.next();
System.out.println("校验出现错误,字段是:" + next.getPropertyPath() + ",错误的信息是:" + next.getMessage());
}
}
}
4.对象图
Bean Validation API不仅能够用来校验单个的实例对象,还能够用来校验完整的对象图。要使用这个功能,只需要在一个有关联关系的字段或者属性上标注@Valid。这样,如果一个对象被校验,那么它的所有标注了@Valid的关联对象都会被校验,一般用于一对一,一对多,多对多中。
- Person.java
package com.miracle.demo3.d_map;
import javax.validation.constraints.NotNull;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
public class Person {
@NotNull
private String name;
public Person(@NotNull String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- Car.java
package com.miracle.demo3.d_map;
import com.miracle.demo3.a_helloworld.CarChecks;
import javax.validation.*;
import javax.validation.constraints.NotNull;
import java.util.Iterator;
import java.util.Set;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
public class Car {
/**
这里如果想 验证Car时,同时验证Person
需要加 @Valid 注解
*/
@NotNull
@Valid
private Person person;
public Car(@NotNull @Valid Person person) {
this.person = person;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public static void main(String[] args) {
// 创建校验工厂
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
// 获取校验对象
Validator validator = factory.getValidator();
// 使用校验对象,校验对象模型(Domain Model)
Person person = new Person(null);
Car car = new Car(person);
/**
* car : 要校验哪个对象
* CarChecks.class : 要校验的组
*/
Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car);
Iterator<ConstraintViolation<Car>> iterator = constraintViolations.iterator();
System.out.println("有" + constraintViolations.size() + "条校验出错的信息");
while (iterator.hasNext()){
ConstraintViolation<Car> next = iterator.next();
System.out.println("校验出现错误,字段是:" + next.getPropertyPath() + ",错误的信息是:" + next.getMessage());
}
}
}
四.创建一个自定义校验规则
尽管Bean Validation API定义了一大堆标准的约束条件,但是肯定还是有些约束不能满足我们需求的时候,在这种情况下,你可以根据你的特定的校验需求来创建自己的约束条件。
按照一下三个步骤来创建一个自定义的约束条件
- 创建约束标注
- 实现一个验证器
- 定义默认的验证错误信息
案例:
让我们来创建一个新的用来判断一个给定字符串是否全是大写或者小写字符的约束标注。
我们将稍后把它用在Car类licensePlate字段上来确保这个字段的内容出现的字母都是大写字母,
因为车牌都是大写字母组成。首先,我们需要一种方法来表示这两种模式,我们可以使用String常量,
枚举类型是个更好的选择:
1.demo
package com.miracle.demo3.e_personalbean;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
/*
枚举类型,用来指定2个属性,大写,小写
*/
public enum CaseMode {
UPPER,
LOWER;
}
package com.miracle.demo3.e_personalbean;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
/*
定义一个注解类,@CheckCase
@Target:表示目标,此注解可以作用在哪个位置上
@Retention:这个注解保留到哪个时期(运行期,源码期)
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface CheckCase {
String message() default "车牌号码必须使用大写字母";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
CaseMode value();
}
package com.miracle.demo3.e_personalbean;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
/**
* ConstraintValidator 定义了两个泛型的参数
* * 第一个是这个校验器所服务的标注类型
* * 第二个这个校验器所支持的被校验元素的类型,@CheckCase,放置到String类型的字段上
*/
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
private CaseMode value;
private String message;
/*
初始化需要完成的方法
*/
@Override
public void initialize(CheckCase constraintAnnotation) {
// 获取 @CheckCase(value=CaseMode.UPPER)定义的value属性值
this.value = constraintAnnotation.value();
// 取出 @CheckCase(message = "aaa")定义的message属性值
this.message = constraintAnnotation.message();
}
/*
表示处理校验的方法,返回值boolean类型
s:表示传递的值
return true:表示通过校验
return false:表示没有通过校验,输出错误的信息
*/
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
constraintValidatorContext.disableDefaultConstraintViolation();
constraintValidatorContext.buildConstraintViolationWithTemplate(message).addPropertyNode("").addConstraintViolation();
if (s == null){
return true; // 表示@NotNull去校验输出
}
if (value == CaseMode.UPPER){
return s.equals(s.toUpperCase());
}else {
return s.equals(s.toLowerCase());
}
}
}
测试类
package com.miracle.demo3.e_personalbean;
import com.miracle.demo3.d_map.Person;
import javax.validation.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Iterator;
import java.util.Set;
/**
* Title:
* Description:
* Company:东软集团股份有限公司(NEUSOFT)
* Department: 医疗IT事业部开发部
*
* @Author: Miracle
* email: 350148888@qq.com
*/
public class Car {
@NotNull
@Size(min = 2, max = 14)
@CheckCase(value = CaseMode.UPPER, message = "aaa")
private String licensePlate;
public Car(String licensePlate) {
this.licensePlate = licensePlate;
}
public static void main(String[] args) {
// 创建校验工厂
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
// 获取校验对象
Validator validator = factory.getValidator();
// 使用校验对象,校验对象模型(Domain Model)
Car car = new Car("miracle");
/**
* car : 要校验哪个对象
* CarChecks.class : 要校验的组
*/
Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car);
Iterator<ConstraintViolation<Car>> iterator = constraintViolations.iterator();
System.out.println("有" + constraintViolations.size() + "条校验出错的信息");
while (iterator.hasNext()){
ConstraintViolation<Car> next = iterator.next();
System.out.println("校验出现错误,字段是:" + next.getPropertyPath() + ",错误的信息是:" + next.getMessage());
}
}
}