Bean Validation

本文介绍了Bean Validation规范,用于简化对象模型的验证。通过注解实现数据约束,提供API进行对象验证,支持自定义错误消息和方法级别的注解验证。在Java EE 8中作为标准部分,适用于服务器端和客户端应用。示例代码展示了最基本的用法、自定义错误信息以及在方法上添加验证注解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

资源链接

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方法上添加相应的注解,其实这和在字段中添加注解一样,都是可用的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值