最近写业务代码,因为页面复杂,导致对应的Bean属性非常多,而产品大佬又提出各种校验要求。

emmmmmm......如果写if条件来校验,那简直是又臭又长。

所以就有今天的话题——利用注解对Bean进行校验。

利用注解对Bean进行校验,主要是利用hibernate-validator框架,hibernate-validator实现了validation-api的接口关于Bean校验的接口,直接使用hibernate-validator非常方便,省时省力。


1.hibernate-validato简介

此hibernate-validator+SpringMVC可以实现以下功能:

  • 注解java bean声明校验规则;
  • 添加message错误信息源实现国际化配置;
  • 结合spring form中的errors标签展现错误信息。

优势:

  • 代码简洁、处理校验可以更加优雅。

实现要求:

  • 使用hibernate validator至少要引入两个jar包:
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>6.0.1.Final</version>
    </dependency>
    
    <dependency>
        <groupId>org.glassfish</groupId>
        <artifactId>javax.el</artifactId>
        <version>3.0.1-b08</version>
    </dependency>
  • java bean中使用注解添加检验规则。

2.注解及用法

hibernate validator使用的注解定义在hibernate-validator-6.0.1.Final.jar、validation-api-1.1.0.Final.jar两个包中,

@AssertFalse 验证注解的元素值是false
@AssertTrue 验证注解的元素值是true
@DecimalMax(value=x) 验证注解的元素值小于等于@ DecimalMax指定的value值
@DecimalMin(value=x) 验证注解的元素值小于等于@ DecimalMin指定的value值
@Digits(integer=整数位数, fraction=小数位数) 验证注解的元素值的整数位数和小数位数上限
@Future 验证注解的元素值(日期类型)比当前时间晚
@Max(value=x) 验证注解的元素值小于等于@Max指定的value值
@Min(value=x) 验证注解的元素值大于等于@Min指定的value值
@NotNull 验证注解的元素值不是null
@Null 验证注解的元素值是null
@Past 验证注解的元素值(日期类型)比当前时间早
@Pattern(regex=正则表达式, flag=) 验证注解的元素值与指定的正则表达式匹配
@Size(min=最小值, max=最大值) 验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小
@Valid 验证关联的对象,如账户对象里有一个订单对象,指定验证订单对象
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@Range(min=最小值, max=最大值) 验证注解的元素值在最小值和最大值之间
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Length(min=下限, max=上限) 验证注解的元素值长度在min和max区间内
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

3.校验demo

利用hibernate validator实现Bean校验,一般有两种做法:

  • 利用@ModelAttribute和@Valid注解;
  • 利用validate()、validateValue()、validateProperty()等方法在需要之处直接调用。
Bean对象:
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

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;
    }

    //getters and setters ...
}

3.1.利用Validator的validate()方法

验证器
import org.apache.commons.collections4.CollectionUtils;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;

public class ValidateTest {

    /**
     * 验证器
     */
    private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
    
    public static void main(String[] args) {
        Car car = new Car(null, "12", 1);
        //执行验证
        Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car);
        //打印校验信息
        if (CollectionUtils.isNotEmpty(constraintViolations)) {
            for (ConstraintViolation<Car> constraintViolation : constraintViolations) {
                System.out.println(constraintViolation.getPropertyPath().toString() + ": " + constraintViolation.getMessage());
            }
        }
    }
}
输出结果:
seatCount: 最小不能小于2
manufacturer: 不能为null

3.2.利用@ModelAttribute和@Valid注解和BindingResult对象

校验demo
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.validation.Valid;

@Controller("/Validate")
public class ValidateTestController {
    @RequestMapping("/demo1")
    public void validateDemo(@Valid @ModelAttribute("car") Car car, BindingResult result) {
        if(result.hasErrors()){
            for (ObjectError error : result.getAllErrors()) {
                System.out.println(error.getDefaultMessage());
            }
        }
    }
}
测试数据:
{
    "manufacturer": null,
    "licensePlate": "",
    "seatCount": 1
}
结果:
不能为null
个数必须在2和14之间
最小不能小于2
注意,需要加上@ModelAttribute注解,否则会报错: An Error/BindingResult argument is
expected to be declared immediately after the model attribute

3.3.利用@Valid注解和统一异常处理

校验demo
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.validation.Valid;

@Controller
@RequestMapping("/validate")
public class ValidateTestController {

    @RequestMapping("/demo1")
    public void validateDemo(@Valid Car car) { }
}
测试数据:
{
    "manufacturer": null,
    "licensePlate": "",
    "seatCount": 1
}
统一异常处理类:
@Component
public class ExceptionResolver implements HandlerExceptionResolver {
    /**
     * 日志
     */
    private static final Logger LOG = LoggerFactory.getLogger(AuditCommonExceptionResolver.class);

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        if (ex instanceof BindException) {
            BindException exs = (BindException) ex;
            for (ObjectError o : exs.getAllErrors()) {
                System.out.println(o.getDefaultMessage());
            }
        } else if (ex instanceof ConstraintViolationException) {
            ConstraintViolationException exs = (ConstraintViolationException) ex;
            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            for (ConstraintViolation<?> item : violations) {
                System.out.println(item.getMessage());
            }
        } else {
            LOG.error("捕获未处理异常,异常信息:{}", ex.getMessage());
        }
        return new ModelAndView("/sys/errorPage.jsp");
    }
}
结果:
不能为null
个数必须在2和14之间
最小不能小于2
注意:我自己测试的时候,捕获的是BindException异常,但是看网上好多文章写的都是捕获ConstraintViolationException异常,

比较以上三种校验方式(准确的说应该是三种hibernate validator的使用方式),可以发现2、3大同小异,使用@Valid可以不用再自己写校验的处理方法,但是方式1可以更加灵活,可以按照自己需求处理和组织校验的返回信息。

4.hibernate validator更详细的使用文档

hibernate validator的更详细的使用说明,可以参考官方文档,以前没关注,这次认真的看了一下,发现hibernate validator的功能还是很强大的,最新的5.0、6.0版本只有英文文档,4.0版本有中文文档。地址如下:https://docs.jboss.org/hibern...

5.使用过程中遇到的问题

使用方式1,在我自己电脑注解没有添加message信息时,默认返回的是中文,如@NotNull:不能为null,@NotEmpty:不能为空等等,但是,当部署到服务器以后,就变成英文,如@NotNull:may not be null,@NotEmpty:may not be empty等等,很是诡异。后来研读源码发现,hibernate validator通过messageInterpolator字段来实现国际化的,messageInterpolator字段中存放着当前操作系统的语言环境参数,然后根据语言环境参数去对应的配置文档中读取默认提示信息,而messageInterpolator则会在项目启动时初始化,因为我自己的电脑是中文环境,而公司的系统环境是英文的,所以导致这个问题。


莫小点还有救
219 声望26 粉丝

优秀是一种习惯!