资源链接
Bean Validation 官网地址:
Hibernate Validator 官方文档地址:
validation api 文档地址:
码云测试代码地址:
Bean Validation简介
验证数据是在所有应用程序层中出现的常见任务,从表示到持久化层。通常,在每个层中实现相同的验证逻辑,这是耗时且容易出错的。为了避免重复这些验证,开发人员经常将验证逻辑直接绑定到域模型中,使用验证代码将域类与类本身的元数据绑定在一起。
JSR 380-Bean验证2.0-为实体和方法验证定义了元数据模型和API。默认的元数据源是注释,可以通过使用XML覆盖和扩展元数据。API不绑定到特定的应用程序层或编程模型。它不是与web或持久性层绑定的,它既可以用于服务器端应用程序编程,也可以用于富客户端Swing应用程序开发人员。
Hibernate Validator 6和Bean Validation 2.0需要Java 8或更高版本。
Bean Validation的特点:
Bean Validation 验证符合标准的Java规范
让您通过注释来表达对对象模型的约束
让您以可扩展的方式编写自定义约束
提供用于验证对象和对象图的api
提供用于验证参数和返回方法和构造函数值的api
报告一组违规(本地化)
运行在Java SE上,但是集成在Java EE 6和以后;Bean验证2.0是Java EE 8的一部分
Bean Validation的配置
我们以Maven项目为例来进行说明,Bean Validation在使用时需要添加以下依赖项:分别是javax.validation
中的validation-api
以及org.hibernate.validator
中的hibernate-validator
。
而下面的org.glassfish
中的javax.el
不属于Bean Validation,但是在一些情况下它是必须的,因为Bean Validation需要EL表达式相应的支持,当然,对于提供EL表达式的容器,该依赖就可以不用添加了。
<!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.7.Final</version>
</dependency>
<!--统一的EL参考实现的Maven依赖关系-->
<!--对于那些无法提供EL实现的环境,Hibernate验证器提供了一个章节的相应介绍。然而,这个插值器的使用不符合Bean验证规范。-->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b08</version>
</dependency>
下面是完整的pom.xml源码:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lyc</groupId>
<artifactId>validation</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>validation</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.7.Final</version>
</dependency>
<!--统一的EL参考实现的Maven依赖关系-->
<!--对于那些无法提供EL实现的环境,Hibernate验证器提供了一个章节的相应介绍。然而,这个插值器的使用不符合Bean验证规范。-->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b08</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<!-- lombok 插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
<!--logback-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.8</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.8</version>
</dependency>
<!--slf4j不属于logback,但是通常是slf4j配合logback一起来进行使用的-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.22</version>
</dependency>
</dependencies>
</project>
Bean Validation的用法
最简单的用法
我们以下面的官网示例代码Car.java
为例来进行说明:
package com.lyc.validator.entity;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Getter
@Setter
public class Car {
@NotNull
private String manufacturer;
@NotNull
@Size(min = 2, max = 14)
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;
}
}
其中@NotNull, @Size 和 @Min注解用于声明应用于Car实体类的字段约束条件。
- manufacturer:必须不能为空
- licensePlate:必须不能为空,同时还应满足字符串长度介于2和14个长度之间
- seatCount:必须至少为2
下面是创建的对于Car实体类的测试代码:
package com.lyc.validator;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import com.lyc.validator.entity.Car;
import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@Slf4j
public class CarTest {
private static Validator validator;
@BeforeClass
public static void setUpValidator() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
@Test
public void manufacturerIsNull() {
Car car = new Car( null, "DD-AB-123", 4 );
Set<ConstraintViolation<Car>> constraintViolations =
validator.validate( car );
assertEquals( 1, constraintViolations.size() );
String errMessage = constraintViolations.iterator().next().getMessage();
assertEquals( "不能为null", errMessage);
log.info(errMessage);
}
@Test
public void licensePlateTooShort() {
Car car = new Car( "Morris", "D", 4 );
Set<ConstraintViolation<Car>> constraintViolations =
validator.validate( car );
assertEquals( 1, constraintViolations.size() );
String errMessage = constraintViolations.iterator().next().getMessage();
assertEquals("个数必须在2和14之间", errMessage);
log.info(errMessage);
}
@Test
public void seatCountTooLow() {
Car car = new Car( "Morris", "DD-AB-123", 1 );
Set<ConstraintViolation<Car>> constraintViolations =
validator.validate( car );
assertEquals( 1, constraintViolations.size() );
String errMessage = constraintViolations.iterator().next().getMessage();
assertEquals("最小不能小于2", errMessage);
log.info(errMessage);
}
@Test
public void carIsValid() {
Car car = new Car( "Morris", "DD-AB-123", 2 );
Set<ConstraintViolation<Car>> constraintViolations =
validator.validate( car );
assertEquals( 0, constraintViolations.size() );
}
}
在setUp()方法中,从ValidatorFactory检索验证器对象。一个验证器实例是线程安全的,并且可以多次重用。因此,它可以安全地存储在静态字段中,并在测试方法中使用,以验证不同的Car实例。
validate()方法返回一组约束条件实例,您可以对这些实例进行迭代,以查看发生了哪些验证错误。前三个测试方法显示了一些预期的约束违反,最后一个则是展示的正确的测试。
自定义错误提示信息
Bean Validation在默认的情况下有一些自带的提示信息,如果我们感觉该提示信息并不是很友好,那么我们可以自定义自己的提示信息内容,下面我们以创建实体类Car2为例来进行说明。
package com.lyc.validator.entity;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
@Getter
@Setter
public class Car2 {
@NotNull(message = "manufacturer字段不能为空!")
private String manufacturer;
@AssertTrue(message = "isRegistered字段必须为true!")
private boolean isRegistered;
public Car2(String manufacturer, boolean isRegistered) {
this.manufacturer = manufacturer;
this.isRegistered = isRegistered;
}
}
下面是对Car2的测试代码:
package com.lyc.validator;
import com.lyc.validator.entity.Car2;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;
@Slf4j
public class Car2Test {
private static Validator validator;
@BeforeClass
public static void setUpVolidator(){
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validator = (Validator) validatorFactory.getValidator();
}
@Test
public void manufacturerNull(){
Car2 car2 = new Car2(null,true);
Set<ConstraintViolation<Car2>> constraintViolations= validator.validate(car2);
Assert.assertEquals(1,constraintViolations.size());
String errorMessage = constraintViolations.iterator().next().getMessage();
log.info(errorMessage);
Assert.assertEquals("manufacturer字段不能为空!",errorMessage);
}
@Test
public void AssertToTrue(){
Car2 car2 = new Car2("Morris",false);
Set<ConstraintViolation<Car2>> constraintViolations= validator.validate(car2);
Assert.assertEquals(1,constraintViolations.size());
String errorMessage = constraintViolations.iterator().next().getMessage();
log.info(errorMessage);
Assert.assertEquals("isRegistered字段必须为true!",errorMessage);
}
}
在上面的代码中,我们通过在注解的后面添加参数message,将我们想要的输出信息写入即可,这样在校验Car2中的信息时,默认的错误提示信息会被我们所写的所覆盖,这样就可以一旦有错误信息时,我们便可以第一时间查看到相应的信息。
在方法上面添加注解
上面的示例中,所添加注解的地点都是在字段之中进行的,其实我们除了可以在字段中添加注解之外,我们其实还是可以在方法中添加注解信息的,如下所示的Car3实体类中的实例就是如此。
package com.lyc.validator.entity;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
public class Car3 {
private String manufacturer;
private boolean isRegistered;
public Car3(String manufacturer, boolean isRegistered) {
this.manufacturer = manufacturer;
this.isRegistered = isRegistered;
}
@NotNull(message = "manufacturer字段不能为空!")
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
@AssertTrue(message = "isRegistered字段返回值为true!")
public boolean isRegistered() {
return isRegistered;
}
public void setRegistered(boolean isRegistered) {
this.isRegistered = isRegistered;
}
}
下面是对Car3的测试源码。
package com.lyc.validator;
import com.lyc.validator.entity.Car3;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;
@Slf4j
public class Car3Test {
private static Validator validator;
@BeforeClass
public static void setUpVolidator(){
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validator = (Validator) validatorFactory.getValidator();
}
@Test
public void manufacturerNull(){
Car3 car3 = new Car3(null,true);
Set<ConstraintViolation<Car3>> constraintViolations= validator.validate(car3);
Assert.assertEquals(1,constraintViolations.size());
String errorMessage = constraintViolations.iterator().next().getMessage();
log.info(errorMessage);
Assert.assertEquals("manufacturer字段不能为空!",errorMessage);
}
@Test
public void AssertToTrue(){
Car3 car3 = new Car3("Morris",false);
Set<ConstraintViolation<Car3>> constraintViolations= validator.validate(car3);
Assert.assertEquals(1,constraintViolations.size());
String errorMessage = constraintViolations.iterator().next().getMessage();
log.info(errorMessage);
Assert.assertEquals("isRegistered字段返回值为true!",errorMessage);
}
}
如上所示,我们通过在实体类的get方法上添加相应的注解,其实这和在字段中添加注解一样,都是可用的。