Bean Validation在SpringMVC中的应用

文章目录

  • WHAT
    • Bean Validation 1.0
    • Bean Validation 1.1
    • Bean Validation 2.0
  • WHY
  • HOW
    • 约束
      • 基本约束
      • 嵌套约束
      • 分组约束
      • 方法参数约束
    • 集成
      • 扩展
      • Controller方法参数校验
      • 校验任意方法
      • @Valid VS @Validated
  • 参考

WHAT

Bean Validation 是一个java规范。可以通过注解的方式约束定义的对象模型或约束方法的入参和出参对象

Bean Validation 1.0

Bean Validation 1.0(JSR303)是最早的一版java标准对象验证规范,是Java EE 6的一部分。认证的具体实现有:

名称 版本
Hibernate Validator 4.3.1.Final
Apache BVal 0.5

Bean Validation 1.1

Bean Validation 1.1(JSR349)是Java EE 7的一部分。
认证的具体实现有:

名称 版本
Hibernate Validator 4.3.1.Final
Apache BVal 1.1.2

较JSR303新增

  • 方法级别上的约束,包括入参、出参
  • DI and CDI(JSR346) 集成
  • 支持组转换
  • 违反约束消息支持EL表达式(JSR341)
  • 集成其他规范,如验证 JAX-RS: Java API for RESTful Web Services
  • more details

Bean Validation 2.0

Bean Validation 2.0(JSR380)是Java EE 8的一部分。
认证的具体实现有:

名称 版本
Hibernate Validator 6.0.1.Final

较JSR380新增

  • 可针对容器中的元素进行约束
  • 支持data/time 的@Past、@Future约束
  • 新的内置约束注解,如:@Email, @NotEmpty, @NotBlank, @Positive, @PositiveOrZero, @Negative, @NegativeOrZero, @PastOrPresent and @FutureOrPresent
  • 可以通过反射取得字段值(field的注解约束不需要实现getter方法也可以进行约束校验,1.0和1.1必须实现getter方法)
  • more details

WHY

为什么要使用Bean Validation ?

验证参数 BEFORE

        Book book = new Book();
        if (book != null 
                && book.getCategory() != null
                && book.getCategory().trim().length() != 0
                && book.getTitle() != null
                && book.getTitle().trim().length() != 0
                && book.getWriter() != null
                && book.getWriter().trim().length() != 0) {
            // TODO
        }
       

验证参数 AFTER

public class Book {

    @NotBlank
    private String title;
    @NotBlank
    private String writer;
    @NotBlank
    private String category;

    // setter/getter
 }
 
 validator.validate(new Book())
     
  • 减少频繁、冗余的if判断
  • Constrain once,validate everywhere

HOW

Bean Validation 怎么用?

  • Requirements(选择 Hibernate Validator实现)
MAVEN依赖:

        
            org.hibernate
            hibernate-validator
            6.0.1.Final
        
        
            org.glassfish
            javax.el
            3.0.1-b06
        

约束

常用的注解约束解释

基本约束

public class BeanForValidate {

    /**
     * 元素: CharSequence
     * 约束:不能为null, 至少包含一个非空字符
     */
    @NotBlank
    private String blankStr = "  ";
    
    
    /**
     * 元素:CharSequence(length)、Collection(size)、Map(size)、Array(length)
     * 约束:不能为null, 不能为空
     */
    @NotEmpty
    private String emptyStr = "  ";
    @NotEmpty
    private List emptyList;
    

    /**
     * 元素:BigDecimal、BigInteger、byte、short、int、long(包括原型的包装类)
     * 约束:min <= element <=max , null 合法
     */
    @Min(value = 11)
    @Max(value = 21)
    // 11<=size<=21
    private Integer minMaxInt = 10;
    @Min(value = 21)
    private BigDecimal minDecimal = new BigDecimal("20.9");
    
    
    /**
     * 元素:BigDecimal、BigInteger、CharSequence、byte、short、int、long(包括原型的包装类)
     * 约束:element >= DecimalMin, null 合法
     */
    @DecimalMin(value = "100.2")
    private String decimalMinDecimal = "100.1";
    
    
    /**
     * 元素:CharSequence(length)、Collection(size)、Map(size)、Array(length)
     * 约束:size(min) <= element >= size(max),null 合法
     */
    @Size(min = 1, max = 3)
    // 1<=size<=3
    private String sizeStr = "size";
    @Size(min = 1)
    @NotNull
    private List nullSizeList = new ArrayList<>();

    {
        nullSizeList.add("A");
        nullSizeList.add("B");
    }
    

    /**
     * 元素:BigDecimal、BigInteger、byte、short、int、long、float、double(包括原型的包装类)
     * 约束:必须是正数, 0非法 ,null 合法
     */
    @Positive
    private Integer positive = -1;
    
    
    /**
     * 元素:BigDecimal、BigInteger、CharSequence、byte、short、int、long(包括原型的包装类)
     * 约束:element整数位数=integer , element小数位数=fraction ,null 合法
     */
    @Digits(integer = 3, fraction = 2)
    // 只允许在3位整数和2位小数范围内
    private String digits = "99.212";
    
    
    /**
     * 元素:Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、
     * OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate
     * 约束:时间是在未来,当前时间默认的是 JVM的时间,null 合法
     */
    @Future
    private Date passTime;
    {
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DAY_OF_YEAR, -1);
        passTime = instance.getTime();
    }
    

    /**
     * 元素:CharSequence
     * 约束:是否符合正则表达式,null 合法
     */
    @Pattern(message = "身份证账号格式错误", regexp = "(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$)/")
    // 身份证号验证
    private String idCard = "92002199902137720";
    @Email
    @NotBlank
    private String mail = "[email protected]";
}


校验:

        BeanForValidate beanForValidate = new BeanForValidate();
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        Set> result = validator.validate(beanForValidate);
        List message
                = result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .collect(Collectors.toList());
                               

结果:

positive must be greater than 0: -1
minMaxInt 最小不能小于11: 10
minDecimal 最小不能小于21: 20.9
decimalMinDecimal 必须大于或等于100.2: 100.1
idCard 身份证账号格式错误: 92002199902137720
sizeStr 个数必须在1和3之间: size
emptyList 不能为空: null
passTime 需要是一个将来的时间: Fri Apr 19 18:06:18 CST 2019
blankStr 不能为空:   
digits 数字的值超出了允许范围(只允许在3位整数和2位小数范围内): 999.212

嵌套约束

public class BeanGraphForValidate {

    @NotEmpty
    private List<@Valid BeanForValidate> beanForValidates; // Bean Validation 2.0

//    OR

//    @NotEmpty
//    @Valid  嵌套校验需要指定 @Valid注解
//    private List beanForValidatess;

    @NotEmpty
    private Map beanForValidateMap; // Bean Validation 2.0

//    OR

//    @NotEmpty
//    @Valid
//    private Map beanForValidateMapM;

    {
        beanForValidates = new ArrayList<>();
        BeanForValidate beanForValidate = new BeanForValidate();
        beanForValidates.add(beanForValidate);
        beanForValidateMap = new HashMap<>();
        beanForValidateMap.put("map", beanForValidate);
    }

}


校验:

        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
        .configure().failFast(false).buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();        
        BeanGraphForValidate beanGraphForValidate = new BeanGraphForValidate();
        Set> result = validator.validate(beanGraphForValidate);
        List message
                = result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .collect(Collectors.toList());

        for (String str : message) {
            System.out.println(str);
        }
   

结果:

beanForValidateMap[map].emptyList 不能为空: null
beanForValidates[0].sizeStr validated size size: size
beanForValidateMap[map].blankStr 不能为空:   
beanForValidateMap[map].property 不能为空: null
beanForValidateMap[map].passTime 需要是一个将来的时间: Mon Apr 22 00:44:45 CST 2019
beanForValidates[0].minDecimal the element is 20.9, but need element < 21: 20.9
beanForValidateMap[map].minMaxInt 最小不能小于11: 10
beanForValidates[0].decimalMinDecimal 必须大于或等于100.2: 100.1
beanForValidateMap[map].idCard 身份证账号格式错误: 92002199902137720
beanForValidates[0].positive must be greater than 0: -1
beanForValidateMap[map].digits 数字的值超出了允许范围(只允许在3位整数和2位小数范围内): 99.212
beanForValidates[0].minMaxInt 最小不能小于11: 10
beanForValidateMap[map].minDecimal the element is 20.9, but need element < 21: 20.9
beanForValidates[0].idCard 身份证账号格式错误: 92002199902137720
beanForValidates[0].property 不能为空: null
beanForValidates[0].passTime 需要是一个将来的时间: Mon Apr 22 00:44:45 CST 2019
beanForValidateMap[map].sizeStr validated size size: size
beanForValidates[0].blankStr 不能为空:   
beanForValidates[0].digits 数字的值超出了允许范围(只允许在3位整数和2位小数范围内): 99.212
beanForValidateMap[map].positive must be greater than 0: -1
beanForValidateMap[map].decimalMinDecimal 必须大于或等于100.2: 100.1
beanForValidates[0].emptyList 不能为空: null

分组约束

public class BeanForGroupValidate {

    interface ListGroup {
    }

    interface MapGroup {
    }

    @NotNull(groups = ListGroup.class, message = "listGroup") // 用于指定需要校验的组
    private List list;

    @NotNull(groups = MapGroup.class, message = "mapGroup")
    private Map map;

    @NotNull(message = "belong defaultGroup") // 未指明分组则为缺省组 Default.class
    private Map defaultMap;
}


校验:
validator.validate(beanGraphForValidate)
结果:
defaultMap belong to defaultGroup: null

校验:validator.validate(beanGraphForValidate, BeanForGroupValidate.ListGroup.class)
结果:
list listGroup: null

校验:
validator.validate(beanGraphForValidate, BeanForGroupValidate.MapGroup.class)
结果:
map mapGroup: null

方法参数约束

  • 入参
  • 出参
  • 构造器参数
  • 构造器返回
public class MethodValidate {

    @NotBlank
    private String name;

    @NotNull
    private Integer age;

    @NotNull
    public MethodValidate(String name) {
        this.name = name;
    }


    public MethodValidate(@NotNull Integer age) {
        this.age = age;
    }

    @Size(min = 2, max = 2)
    public List getPeopleName() {
        List peoples = new ArrayList<>();
        peoples.add(name);
        return peoples;
    }

    public void setPeopleName(@NotBlank String name) {
        this.name = name;
    }

}


校验:

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        ExecutableValidator executableValidator = factory.getValidator().forExecutables();

        MethodValidate methodValidate = new MethodValidate("Mary");
        Method method = MethodValidate.class.getMethod( "setPeopleName", String.class );
        Object[] parameterValues = { "" };
        Set> validateSetPeopleName = executableValidator.validateParameters(
                methodValidate,
                method,
                parameterValues
        );

        List messages
                = validateSetPeopleName.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .collect(Collectors.toList());
        System.out.println(messages);


        method = MethodValidate.class.getMethod("getPeopleName");
        List peoples = new ArrayList<>();
        peoples.add("Mary");
        Set> validateGetPeopleName = executableValidator.validateReturnValue(
                methodValidate,
                method,
                peoples
        );
        messages
                = validateGetPeopleName.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .collect(Collectors.toList());
        System.out.println(messages);


        Constructor constructorParams = MethodValidate.class.getConstructor(Integer.class);
        Object[] params = {null};
        Set> validateConstructor = executableValidator.validateConstructorParameters(
                constructorParams,
                params
        );
        messages
                = validateConstructor.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .collect(Collectors.toList());
        System.out.println(messages);


        Constructor constructorReturn = MethodValidate.class.getConstructor(String.class);
        MethodValidate createdBean = new MethodValidate(18);
        Set> validateConstructorReturn = executableValidator.validateConstructorReturnValue(
                constructorReturn,
                createdBean
        );
        messages
                = validateConstructorReturn.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .collect(Collectors.toList());
        System.out.println(messages);
        

结果:

[setPeopleName.arg0 不能为空: ]
[getPeopleName. 个数必须在2和2之间: [Mary]]
[MethodValidate.arg0 不能为null: null]
[]

集成

如何将Bean Validation集成到Spring MVC框架中?

  • Requirements
MAVEN依赖:

        
            org.hibernate
            hibernate-validator
            6.0.1.Final
        
        
            org.glassfish
            javax.el
            3.0.1-b06
        

扩展

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
	@Bean
	public Validator validator() {
		ValidatorFactory vf = Validation.byProvider(HibernateValidator.class)
				.configure().failFast(true)
				.buildValidatorFactory();
		return vf.getValidator();
	}

Controller方法参数校验

  • Requirements
    Spring 容器中需要创建Bean:LocalValidatorFactoryBeanLocalValidatorFactoryBean包含Bean Validation的实现类(通过SPI加载Bean Validation的实现,本文使用hibernate-validator实现)

  • 使用

    @RequestMapping(value = "/beanValidation", method = RequestMethod.POST)
    public void testBeanValidation(@Valid BeanForValidate beanForValidate)
  • 原理

SpringMVC在创建RequestMappingHandlerAdapter时会将OptionalValidatorFactoryBean设置到WebBindingInitializer属性中,RequestResponseBodyMethodProcessor在解析参数的时候通过WebBindingInitializerOptionalValidatorFactoryBean绑定到WebDataBinder中,WebDataBinder通过判断参数是否含有@Valid或@Validated来决定是否进行参数校验

  • 异常
    MethodArgumentNotValidException

校验任意方法

  • Requirements
    Spring 容器中需要创建Bean:MethodValidationPostProcessor

  • 使用

@Validated
public interface BeanValidationService {
    void testBeanValidation(@Valid BeanForValidate beanForValidate);
  • 原理
    MethodValidationPostProcessor会对含有@Validated的类或接口做AOP增强
  • 异常
    ConstraintViolationException

@Valid VS @Validated

  • @Valid
    由validation-api定义,主要用于Bean的嵌套验证使用,也可用于判断Controller方法中的参数是否需要Bean Validation
  • Validated
    由Spring定义,用于判断Controller方法中的参数是否需要Bean Validation以及用于开启Bean Validation的方法增强的标志。@Validated能够指定validation groups

参考

我的博客

你可能感兴趣的:(java,Spring)