<导航

spring validation校验参数

一、前言  

  数据的校验是交互式网站一个不可或缺的功能,前端的js校验可以涵盖大部分的校验职责,如用户名唯一性,生日格式,邮箱格式校验等等常用的校验。但是为了避免用户绕过浏览器,使用http工具直接向后端请求一些违法数据,服务端的数据校验也是必要的,可以防止脏数据落到数据库中,如果数据库中出现一个非法的邮箱格式,也会让运维人员头疼不已。可以使用本文将要介绍的validation来对数据进行校验。

二、常用校验

1、JSR303/JSR-349

  JSR303是一项标准,只提供规范不提供实现,规定一些校验规范即校验注解,如@Null,@NotNull,@Pattern,位于javax.validation.constraints包下。JSR-349是其的升级版本,添加了一些新特性。

@Null   //被注释的元素必须为null
@NotNull  //被注释的元素必须不为null
@AssertTrue  //被注释的元素必须为true
@AssertFalse  //被注释的元素必须为false
@Min(value)   //被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)   //被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)   //被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)   //被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min)   //被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction)   //被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past       //被注释的元素必须是一个过去的日期
@Future     //被注释的元素必须是一个将来的日期
@Pattern(value)   //被注释的元素必须符合指定的正则表达式

2、hibernate validation 

hibernate validation是对这个规范的实现,并增加了一些其他校验注解,如@Email,@Length,@Range等等

@Email     //被注释的元素必须是电子邮箱地址
@Length(min=, max=)  //被注释的字符串的大小必须在指定的范围内
@NotEmpty            //被注释的字符串的必须非空
@Range(min=, max=)   //被注释的元素必须在合适的范围内
@NotBlank       //字符串不能为null,字符串trin()后也不能等于“”
@URL(protocol=,host=, port=, regexp=, flags=) //被注释的字符串必须是一个有效的url

3、spring validation 

spring validation对hibernate validation进行了二次封装,在springmvc模块中添加了自动校验,并将校验信息封装进了特定的类中。

package com.example.validation.domain;
 
import javax.validation.constraints.Pattern;
 
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;
 
public class User {
    @NotBlank(message = "用户名称不能为空。")
    private String name;
 
    @Range(max = 150, min = 1, message = "年龄范围应该在1-150内。")
    private Integer age;
 
    @NotEmpty(message = "密码不能为空")
    @Length(min = 6, max = 8, message = "密码长度为6-8位。")
    @Pattern(regexp = "[a-zA-Z]*", message = "密码不合法")
    private String password;

三、测试

1、准备工作

引入相关依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
/dependency>

还引入了lombok、SpringBoot的web、test等基础依赖,这里就不一 一给出了。

2、测试所用模型为:

import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.URL;
 
import javax.validation.constraints.*;
import java.util.Date;
import java.util.List;
import java.util.Map;
 
/**
 * Validation注解
 *
 * @author JustryDeng
 * @date 2019/1/15 0:43
 */
 
public class ValidationBeanModel {
 
    @Setter
    @Getter
    public class AbcAssertFalse {
 
        @AssertFalse
        private Boolean myAssertFalse;
    }
 
    @Setter
    @Getter
    public class AbcAssertTrue {
 
        @AssertTrue
        private Boolean myAssertTrue;
    }
 
    @Setter
    @Getter
    public class AbcDecimalMax {
 
        @DecimalMax(value = "12.3")
        private String myDecimalMax;
    }
 
    @Setter
    @Getter
    public class AbcDecimalMin {
 
        @DecimalMin(value = "10.3")
        private String myDecimalMin;
    }
 
    @Setter
    @Getter
    public class AbcDigits {
 
        @Digits(integer = 5, fraction = 3)
        private Integer myDigits;
    }
 
    @Setter
    @Getter
    public class AbcEmail {
 
        @Email
        private String myEmail;
    }
 
    @Setter
    @Getter
    public class AbcFuture {
 
        @Future
        private Date myFuture;
    }
 
    @Setter
    @Getter
    public class AbcLength {
 
        @Length(min = 5, max = 10)
        private String myLength;
    }
 
    @Setter
    @Getter
    public class AbcMax {
 
        @Max(value = 200)
        private Long myMax;
    }
 
    @Setter
    @Getter
    public class AbcMin {
 
        @Min(value = 100)
        private Long myMin;
    }
 
    @Setter
    @Getter
    public class AbcNotBlank {
 
        @NotBlank
        private String myStringNotBlank;
 
        @NotBlank
        private String myObjNotBlank;
    }
 
    @Setter
    @Getter
    public class AbcNotEmpty {
 
        @NotEmpty
        private String myStringNotEmpty;
 
        @NotEmpty
        private String myNullNotEmpty;
 
        @NotEmpty
        private Map<String, Object> myMapNotEmpty;
 
        @NotEmpty
        private List<Object> myListNotEmpty;
 
        @NotEmpty
        private Object[] myArrayNotEmpty;
    }
 
    @Setter
    @Getter
    public class AbcNotNull {
 
        @NotNull
        private String myStringNotNull;
 
        @NotNull
        private Object myNullNotNull;
 
        @NotNull
        private Map<String, Object> myMapNotNull;
    }
 
    @Setter
    @Getter
    public class AbcNull {
 
        @Null
        private String myStringNull;
 
        @Null
        private Map<String, Object> myMapNull;
    }
 
    @Setter
    @Getter
    public class AbcPast {
 
        @Past
        private Date myPast;
    }
 
    @Setter
    @Getter
    public class AbcPattern {
 
        @Pattern(regexp = "\\d+")
        private String myPattern;
    }
 
    @Setter
    @Getter
    public class AbcRange {
 
        @Range(min = 100, max = 100000000000L)
        private Double myRange;
    }
 
    @Setter
    @Getter
    public class AbcSize {
 
        @Size(min = 3, max = 5)
        private List<Integer> mySize;
    }
 
    @Setter
    @Getter
    public class AbcURL {
 
        @URL
        private String myURL;
    }
}
View Code

3、测试方法

import com.aspire.model.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
 
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.*;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class ValidationDemoApplicationTests {
 
    private Validator validator;
 
 
    @Before
    public void initValidator() {
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        validator = validatorFactory.getValidator();
    }
 
    /**
     * 在myAssertTrue属性上加@AssertTrue注解
     * <p>
     * 程序输出:  com.aspire.model.ValidationBeanModel$AbcAssertTrue类的myAssertTrue属性 -> 只能为true
     */
    @Test
    public void testAssertTrue() {
        ValidationBeanModel.AbcAssertTrue vm = new ValidationBeanModel().new AbcAssertTrue();
        vm.setMyAssertTrue(false);
        fa(vm);
    }
 
    /**
     * 在myAssertFalse属性上加@AssertFalse注解
     * <p>
     * 程序输出:  com.aspire.model.ValidationBeanModel$AbcAssertFalse类的myAssertFalse属性 -> 只能为false
     */
    @Test
    public void testAssertFalse() {
        ValidationBeanModel.AbcAssertFalse vm = new ValidationBeanModel().new AbcAssertFalse();
        vm.setMyAssertFalse(true);
        fa(vm);
    }
 
 
    /**
     * 在myDecimalMax属性上加@DecimalMax(value = "12.3")注解
     * <p>
     * 程序输出:  com.aspire.model.ValidationBeanModel$AbcDecimalMax类的myDecimalMax属性 -> 必须小于或等于12.3
     */
    @Test
    public void testDecimalMax() {
        ValidationBeanModel.AbcDecimalMax vm = new ValidationBeanModel().new AbcDecimalMax();
        vm.setMyDecimalMax("123");
        fa(vm);
    }
 
    /**
     * 在myDecimalMin属性上加@DecimalMin(value = "10.3")注解
     * <p>
     * 程序输出:  com.aspire.model.ValidationBeanModel$AbcDecimalMin类的myDecimalMin属性 -> 必须大于或等于10.3
     */
    @Test
    public void testDecimalMin() {
        ValidationBeanModel.AbcDecimalMin vm = new ValidationBeanModel().new AbcDecimalMin();
        vm.setMyDecimalMin("1.23");
        fa(vm);
    }
 
    /**
     * 在myDigits属性上加@Digits(integer = 5, fraction = 3)注解
     * <p>
     * 程序输出:  com.aspire.model.ValidationBeanModel$AbcDigits类的myDigits属性 -> 数字的值超出了允许范围(只允许在5位整数和3位小数范围内)
     */
    @Test
    public void testDigits() {
        ValidationBeanModel.AbcDigits vm = new ValidationBeanModel().new AbcDigits();
        vm.setMyDigits(1000738);
        fa(vm);
    }
 
    /**
     * 在myEmail属性上加@Email注解
     * <p>
     * 程序输出:  com.aspire.model.ValidationBeanModel$AbcEmail类的myEmail属性 -> 不是一个合法的电子邮件地址
     */
    @Test
    public void testEmail() {
        ValidationBeanModel.AbcEmail vm = new ValidationBeanModel().new AbcEmail();
        vm.setMyEmail("asd@.com");
        fa(vm);
    }
 
    /**
     * 在myFuture属性上加@Future注解
     * <p>
     * 程序输出:  com.aspire.model.ValidationBeanModel$AbcFuture类的myFuture属性 -> 需要是一个将来的时间
     */
    @Test
    public void testFuture() {
        ValidationBeanModel.AbcFuture vm = new ValidationBeanModel().new AbcFuture();
        vm.setMyFuture(new Date(10000L));
        fa(vm);
    }
 
    /**
     * 在myLength属性上加@Length(min = 5, max = 10)注解
     * <p>
     * 程序输出:  com.aspire.model.ValidationBeanModel$AbcLength类的myLength属性 -> 长度需要在5和10之间
     */
    @Test
    public void testLength() {
        ValidationBeanModel.AbcLength vm = new ValidationBeanModel().new AbcLength();
        vm.setMyLength("abcd");
        fa(vm);
    }
 
    /**
     * 在myMax属性上加@Max(value = 200)注解
     * <p>
     * 程序输出:  com.aspire.model.ValidationBeanModel$AbcMax类的myMax属性 -> 最大不能超过200
     */
    @Test
    public void testMax() {
        ValidationBeanModel.AbcMax vm = new ValidationBeanModel().new AbcMax();
        vm.setMyMax(201L);
        fa(vm);
    }
 
    /**
     * 在myMin属性上加@Min(value = 200)注解
     * <p>
     * 程序输出:  com.aspire.model.ValidationBeanModel$AbcMin类的myMin属性 -> 最小不能小于100
     */
    @Test
    public void testMin() {
        ValidationBeanModel.AbcMin vm = new ValidationBeanModel().new AbcMin();
        vm.setMyMin(99L);
        fa(vm);
    }
 
    /**
     * 在myStringNotBlank属性上加@NotBlank注解
     * 在myObjNotBlank属性上加@NotBlank注解
     *
     * 注:如果属性值为null 或者 .trim()后等于"",那么会提示 不能为空
     * <p>
     * 程序输出:   com.aspire.model.ValidationBeanModel$AbcNotBlank类的myObjNotBlank属性 -> 不能为空
     *            com.aspire.model.ValidationBeanModel$AbcNotBlank类的myStringNotBlank属性 -> 不能为空
     */
    @Test
    public void testNotBlank() {
        ValidationBeanModel.AbcNotBlank vm = new ValidationBeanModel().new AbcNotBlank();
        vm.setMyObjNotBlank(null);
        vm.setMyStringNotBlank(" ");
        fa(vm);
    }
 
    /**
     * 在myStringNotEmpty属性上加@NotEmpty注解
     * 在myNullNotEmpty属性上加@NotEmpty注解
     * 在myMapNotEmpty属性上加@NotEmpty注解
     * 在myListNotEmpty属性上加@NotEmpty注解
     * 在myArrayNotEmpty属性上加@NotEmpty注解
     *
     * 注:String可以是.trim()后等于""的字符串,但是不能为null
     * 注:MAP、Collection、Array既不能是空,也不能是null
     *
     * <p>
     * 程序输出: com.aspire.model.ValidationBeanModel$AbcNotEmpty类的myNullNotEmpty属性 -> 不能为空
     *           com.aspire.model.ValidationBeanModel$AbcNotEmpty类的myListNotEmpty属性 -> 不能为空
     *           com.aspire.model.ValidationBeanModel$AbcNotEmpty类的myArrayNotEmpty属性 -> 不能为空
     *           com.aspire.model.ValidationBeanModel$AbcNotEmpty类的myMapNotEmpty属性 -> 不能为空
     */
    @Test
    public void testNotEmpty() {
        ValidationBeanModel.AbcNotEmpty vm = new ValidationBeanModel().new AbcNotEmpty();
        vm.setMyStringNotEmpty(" ");
        vm.setMyNullNotEmpty(null);
        vm.setMyMapNotEmpty(new HashMap<>(0));
        vm.setMyListNotEmpty(new ArrayList<>(0));
        vm.setMyArrayNotEmpty(new String[]{});
        fa(vm);
    }
 
    /**
     * 在myStringNotNull属性上加@NotNull注解
     * 在myNullNotNull属性上加@NotNull注解
     * 在myMapNotNull属性上加@NotNull注解
     *
     * 注:属性值可以是空的, 但是就是不能为null
     * <p>
     * 程序输出:   com.aspire.model.ValidationBeanModel$AbcNotNull类的myNullNotNull属性 -> 不能为null
     */
    @Test
    public void testNotNull() {
        ValidationBeanModel.AbcNotNull vm = new ValidationBeanModel().new AbcNotNull();
        vm.setMyStringNotNull("   ");
        vm.setMyNullNotNull(null);
        vm.setMyMapNotNull(new HashMap<>(0));
        fa(vm);
    }
 
    /**
     * 在myStringNull属性上加@Null注解
     * 在myMapNotNull属性上加@Null注解
     *
     * 注:属性值必须是null, 是空都不行
     * <p>
     * 程序输出:   com.aspire.model.ValidationBeanModel$AbcNull类的myMapNull属性 -> 必须为null
     *            com.aspire.model.ValidationBeanModel$AbcNull类的myStringNull属性 -> 必须为null
     */
    @Test
    public void testNull() {
        ValidationBeanModel.AbcNull vm = new ValidationBeanModel().new AbcNull();
        vm.setMyStringNull("   ");
        vm.setMyMapNull(new HashMap<>(0));
        fa(vm);
    }
 
    /**
     * 在myPast属性上加@Past注解
     *
     * <p>
     * 程序输出:   com.aspire.model.ValidationBeanModel$AbcPast类的myPast属性 -> 需要是一个过去的时间
     */
    @Test
    public void testPast() {
        ValidationBeanModel.AbcPast vm = new ValidationBeanModel().new AbcPast();
        vm.setMyPast(new Date(20000000000000000L));
        fa(vm);
    }
 
    /**
     * 在myPattern属性上加@Pattern(regexp = "\\d+")注解
     *
     * <p>
     * 程序输出:   com.aspire.model.ValidationBeanModel$AbcPattern类的myPattern属性 -> 需要匹配正则表达式"\d"
     */
    @Test
    public void testPattern() {
        ValidationBeanModel.AbcPattern vm = new ValidationBeanModel().new AbcPattern();
        vm.setMyPattern("ABC");
        fa(vm);
    }
 
    /**
     * 在myRange属性上加@Range(min = 100, max = 100000000000L)注解
     *
     * <p>
     * 程序输出:   com.aspire.model.ValidationBeanModel$AbcRange类的myRange属性 -> 需要在100和100000000000之间
     */
    @Test
    public void testRange() {
        ValidationBeanModel.AbcRange vm = new ValidationBeanModel().new AbcRange();
        vm.setMyRange(32222222222222222222222222222222.323);
        fa(vm);
    }
 
    /**
     * 在mySize属性上加@Size(min = 3, max = 5)注解
     *
     * <p>
     * 程序输出:   com.aspire.model.ValidationBeanModel$AbcSize类的mySize属性 -> 个数必须在3和5之间
     */
    @Test
    public void testSize() {
        ValidationBeanModel.AbcSize vm = new ValidationBeanModel().new AbcSize();
        List<Integer> list = new ArrayList<>(4);
        list.add(0);
        list.add(1);
        vm.setMySize(list);
        fa(vm);
    }
 
    /**
     * 在myURL属性上加@URL注解
     *
     * <p>
     * 程序输出:   com.aspire.model.ValidationBeanModel$AbcURL类的myURL属性 -> 需要是一个合法的URL
     */
    @Test
    public void testURL() {
        ValidationBeanModel.AbcURL vm = new ValidationBeanModel().new AbcURL();
        vm.setMyURL("www.baidu.xxx");
        fa(vm);
    }
 
    private <T> void fa(T obj) {
        Set<ConstraintViolation<T>> cvSet = validator.validate(obj);
        for (ConstraintViolation<T> cv : cvSet) {
            System.err.println(cv.getRootBean().getClass().getName() + "类的"
                    + cv.getPropertyPath() + "属性 -> " + cv.getMessage());
        }
    }
 
}
View Code

4、@Validated的使用时机

@Validated的使用位置较多(可详见源码),但其主流的使用位置是以下两种:

  • 在Controller层中,放在模型参数对象前。

          当Controller层中参数是一个对象模型时,只有将@Validated直接放在该模型前,该模型内部的字段才会被
   校验(如果有对该模型的字段进行约束的话)。

@RestController
public class JustryDengController {
 
    @RequestMapping(value = "/test/one")
    public String validatioOne(@Validated @RequestBody User user) {
        System.out.println(myDecimalMax.getMyDecimalMax());
        return "one pass!";
    }
}
  • 在Controller层中,放在类上。

           当一些约束是直接出现在Controller层中的参数前时,只有将@Validated放在类上时,参数前的约束才会生效。

@RestController
@Validated
public class  Controller{
     
    @RequestMapping(value = "/test/two")
    public String validatioTwo(@NotBlank  String name) {
        
        return "two pass!";
    }

}

四、spring boot的数据自动校验功能

1、引入依赖

spring-web模块使用了hibernate-validation,并且databind模块也提供了相应的数据绑定功能。

<dependencies>
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

我们只需要引入spring-boot-starter-web依赖即可,如果查看其子依赖,可以发现如下的依赖:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

2、创建需要被校验的实体类

public class Person {
@NotEmpty(message = "name不能为空")
private String name;
@Range(min = 0, max = 100, message = "age不能大于100小于0")
private int age;
 
public String getName() {
    return name;
}
 
public void setName(String name) {
    this.name = name;
}
 
public int getAge() {
    return age;
}
 
public void setAge(int age) {
    this.age = age;
}

3、在Controller中校验数据

springmvc为我们提供了自动封装表单参数的功能,一个添加了参数校验的典型controller如下所示。

@RequestMapping("/test")
public String valid(@Validated Person person, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            System.out.println(fieldError);
        }
        return "fail";
    }
    return "success";
}

值得注意的地方:

  • 参数Persion前需要加上@Validated注解,表明需要spring对其进行校验,而校验的信息会存放到其后的BindingResult中。注意,必须相邻,如果有多个参数需要校验,形式可以如下。valid(@Validated Person person, BindingResult fooBindingResult ,@Validated Bar bar, BindingResult barBindingResult);即一个校验类对应一个校验结果。
  • 校验结果会被自动填充,在controller中可以根据业务逻辑来决定具体的操作,如跳转到错误页面。 一个最基本的校验就完成了.

启动容器测试结果如下:

Field error in object 'person' on field 'age': rejected value [105]; codes [Range.person.age,Range.age,Range.int,Range]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.age,age]; arguments []; default message [age],100,0]; default message [age不能大于100小于0]

4、统一异常处理

  前面那种方式处理校验错误,略显复杂,而且一般网站都会对请求错误做统一的404页面封装,如果数据校验不通过,则Spring boot会抛出BindException异常,我们可以捕获这个异常并使用Result封装返回结果。通过@RestControllerAdvice定义异常捕获类。
Controller类:

@RequestMapping(value = "valid", method = RequestMethod.GET)
public String valid(@Validated Person person) {
    System.out.println(person);
    return "success";
}

统一异常处理类:

@RestControllerAdvice
public class BindExceptionHanlder {

    @ExceptionHandler(BindException.class)
    public String handleBindException(HttpServletRequest request, BindException exception) {
        List<FieldError> allErrors = exception.getFieldErrors();
        StringBuilder sb = new StringBuilder();
        for (FieldError errorMessage : allErrors) {
            sb.append(errorMessage.getField()).append(": ").append(errorMessage.getDefaultMessage()).append(", ");
        }
        System.out.println(sb.toString());
        return sb.toString();
    }


    //或者换种写法
    @ExceptionHandler(value = {Exception.class})
    public Map<String, Object> globalExceptionHandleMethod(Exception ex) {
        Map<String, Object> resultMap = new HashMap<>(4);
        if (ex instanceof ConstraintViolationException) {
            ConstraintViolationException cvExceptionex = (ConstraintViolationException) ex;
            resultMap.put("msg", "@Validated约束在类上,直接校验接口的参数时异常 -> " + cvExceptionex.getMessage());
            resultMap.put("code", "1");
        } else if (ex instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException manvExceptionex = (MethodArgumentNotValidException) ex;
            resultMap.put("msg", "@Validated约束在参数模型前,校验该模型的字段时发生异常 -> " + manvExceptionex.getMessage());
            resultMap.put("code", "2");
        } else {
            resultMap.put("msg", "系统异常");
            resultMap.put("code", "3");
        }
        return resultMap;
    }

  
/**
 *全局捕捉参数校验异常【validation校验】
 */
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultBean handleBindException(HttpServletRequest request, MethodArgumentNotValidException exception) {
    String defaultMessage = exception.getBindingResult().getAllErrors().get(0).getDefaultMessage();
    return ResultBean.error(defaultMessage);
}

}

5、自定义校验注解

@NameValidation

@Documented
@Constraint(validatedBy = NameValidationValidator.class) //指定此注解的实现,即:验证器
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RUNTIME)
public @interface NameValidation {
    String message() default "不是合法的名字";
 
    Class<?>[] groups() default {};
 
    Class<? extends Payload>[] payload() default {};
 
    @Target({PARAMETER, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        NameValidation[] value();
    }
}

校验类NameValidationValidator

public class NameValidationValidator implements ConstraintValidator<NameValidation, String> {
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if ("steven".equalsIgnoreCase(value)) {
            return true;
        }
        String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
        System.out.println("default message :" + defaultConstraintMessageTemplate);
        //禁用默认提示信息
        //context.disableDefaultConstraintViolation();
        //设置提示语
        //context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation();
        return false;
    }
}

在Person类增加新注解

@NotEmpty(message = "name不能为空")
@NameValidation
private String name;

测试

6、分组校验

有些时候一个对象会在多个场景使用,不同场景对该对象中的参数校验需求不同,即有些场景不对参数进行校验。
比如注册时,我们要填写出生日期参数,但是登录时并不需要该参数。

这里可以用到校验分组groups

public class User implements Serializable {

    // 添加2个空接口,用例标记参数校验规则
    /**
     * 注册校验规则
     */
    public interface UserRegisterValidView {
    }

    /**
     * 登录校验规则
     */
    public interface UserLoginValidView {
    }

    private static final long serialVersionUID = 1L;

    @NotBlank(message = "用户名不能为空")
    private String userName;

    @NotBlank(message = "密码不能为空")
    private String password;

    // 若填写了groups,则该参数验证只在对应验证规则下启用
    @Past(groups = { UserRegisterValidView.class }, message = "出生日期不符合要求")
    private Date birthday;

    @DecimalMin(value = "0.1", message = "金额最低为0.1")
    @NotNull(message = "金额不能为空")
    private BigDecimal balance;

    @AssertTrue(groups = { UserRegisterValidView.class, UserLoginValidView.class }, message = "标记必须为true")
    private boolean flag;

    @Min(value = 18, message = "年龄不能小于18")
    private Integer age;
}

例如出生日期参数,我们只在注册场景校验,我们在其他场景(包含default)一律不进行验证

// 若填写了groups,则该参数验证只在对应验证规则下启用
    @Past(groups = { UserRegisterValidView.class }, message = "出生日期不符合要求")
    private Date birthday;

此时的Controller改成

@RequestMapping(value = "/register", method = RequestMethod.POST)
    @ResponseBody//表明对User对象的校验,启用UserRegisterValidView规则
    public CommonResponse register(@Validated(value = { UserRegisterValidView.class }) @RequestBody User user) {
        CommonResponse response = new CommonResponse();
        return response;
    }

 

1. 定义校验分组
//分组一
public interface ValidationGroup1
{
//接口中不需要任何定义
//用户名不能为空 密码长度在6-12之间
}
//分组二
public interface ValidationGroup2
{
//接口中不需要任何定义
//邮件格式不正确
}

2. 在校验规则中添加分组
//分组一: 用户名不能为空
@NotEmpty(message="{user.username}",groups={ValidationGroup1.class})
public String username;

//分组一:密码长度必须在6-12之间
@Length(min=6,max=12,message="{user.password}",groups={ValidationGroup1.class})
public String password;

//分组二:必须符合正则表达式规则
@Email(regexp="^[_a-z0-9]+@([_a-z0-9]+\\.)+[a-z0-9]{2,3}$",message="{user.email}",groups={ValidationGroup2.class})
private String email;

3.在conroller中指定使用的分组校验
//仅使用分组一进行校验
public String insertUser(Model model,@Validated(value={ValidationGroup1.class}) User user,BindingResult bindingResult,Integer uid){}

//仅使用分组二进行校验
public String insertUser(Model model,@Validated(value={ValidationGroup2.class}) User user,BindingResult bindingResult,Integer uid){}

//使用分组一、分组二进行校验
public String insertUser(Model model,@Validated(value={ValidationGroup1.class,ValidationGroup2.class}) User user,BindingResult bindingResult,Integer uid){}
View Code

参数验证 @Validated 和 @Valid 的区别

  Spring Validation验证框架对参数的验证机制提供了@Validated(Spring's JSR-303 规范,是标准 JSR-303 的一个变种),javax提供了@Valid(标准JSR-303规范),配合 BindingResult 可以直接提供参数验证结果。其中对于字段的特定验证注解比如 @NotNull 等网上到处都有,这里不详述

  在检验 Controller 的入参是否符合规范时,使用 @Validated 或者 @Valid 在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同:

1. 分组

@Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制,这个网上也有资料,不详述。@Valid:作为标准JSR-303规范,还没有吸收分组的功能。

2. 注解地方

@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上

@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上

两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。

3. 嵌套验证

在比较两者嵌套验证时,先说明下什么叫做嵌套验证。比如我们现在有个实体叫做Item:

public class Item {
    @NotNull(message = "id不能为空")
    @Min(value = 1, message = "id必须为正整数")
    private Long id;
    @NotNull(message = "props不能为空")
    @Size(min = 1, message = "至少要有一个属性")
    private List<Prop> props;
}

Item带有很多属性,属性里面有属性id,属性值id,属性名和属性值,如下所示:

public class Prop {
    @NotNull(message = "pid不能为空")
    @Min(value = 1, message = "pid必须为正整数")
    private Long pid;
    @NotNull(message = "vid不能为空")
    @Min(value = 1, message = "vid必须为正整数")
    private Long vid;
    @NotBlank(message = "pidName不能为空")
    private String pidName;
    @NotBlank(message = "vidName不能为空")
    private String vidName;
}

属性这个实体也有自己的验证机制,比如属性和属性值id不能为空,属性名和属性值不能为空等。

现在我们有个 ItemController 接受一个Item的入参,想要对Item进行验证,如下所示:

@RestController
public class ItemController {
    @RequestMapping("/item/add")
    public void addItem(@Validated Item item, BindingResult bindingResult) {
        doSomething();
    }
}

  在上图中,如果Item实体的props属性不额外加注释,只有@NotNull和@Size,无论入参采用@Validated还是@Valid验证,Spring Validation框架只会对Item的id和props做非空和数量验证,不会对props字段里的Prop实体进行字段验证,也就是@Validated和@Valid加在方法参数前,都不会自动对参数进行嵌套验证。也就是说如果传的List中有Prop的pid为空或者是负数,入参验证不会检测出来。

  为了能够进行嵌套验证,必须手动在Item实体的props字段上明确指出这个字段里面的实体也要进行验证。由于@Validated不能用在成员属性(字段)上,但是@Valid能加在成员属性(字段)上,而且@Valid类注解上也说明了它支持嵌套验证功能,那么我们能够推断出:@Valid加在方法参数时并不能够自动进行嵌套验证,而是用在需要嵌套验证类的相应字段上,来配合方法参数上@Validated或@Valid来进行嵌套验证。

我们修改Item类如下所示:

public class Item {
    @NotNull(message = "id不能为空")
    @Min(value = 1, message = "id必须为正整数")
    private Long id;
    @Valid // 嵌套验证必须用@Valid
    @NotNull(message = "props不能为空")
    @Size(min = 1, message = "props至少要有一个自定义属性")
    private List<Prop> props;
}

  然后我们在ItemController的addItem函数上再使用@Validated或者@Valid,就能对Item的入参进行嵌套验证。此时Item里面的props如果含有Prop的相应字段为空的情况,Spring Validation框架就会检测出来,bindingResult就会记录相应的错误。

总结一下 @Validated 和 @Valid 在嵌套验证功能上的区别:

@Validated: 用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。

@Valid: 用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。

 

 

参考文章:

https://blog.csdn.net/steven2xupt/article/details/87452664

https://blog.csdn.net/justry_deng/article/details/86571671

https://blog.csdn.net/qq_33144861/article/details/77895366

posted @ 2019-11-02 23:53  字节悦动  阅读(10266)  评论(0编辑  收藏  举报