SpringBoot使用Validation校验参数

    科技2022-08-01  89

    JSR(Java Specification Requests)是Java界的重要标准;JSR又细分很多标准,其中JSR303就代表Bean Validation。更多细节可参考:https://jcp.org/en/jsr/detail?id=303。


    目录

    准备工作

    约束性注解(简单)说明

    @Validated的使用时机

    @Validated与@Valid的简单对比说明

    自定义注解

    对注解抛出的异常进行处理


    欢迎来到JustryDeng的博客!正文start!


    准备工作

    引入相关依赖:

     

    <dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-validation</artifactId>

    </dependency>

    注:本人测试时,还引入了lombok、SpringBoot的web、test等基础依赖,这里就不一一给出了。


    约束性注解(简单)说明

    注解

    功能

    @AssertFalse

    可以为null,如果不为null的话必须为false

    @AssertTrue

    可以为null,如果不为null的话必须为true

    @DecimalMax

    设置不能超过最大值

    @DecimalMin

    设置不能超过最小值

    @Digits

    设置必须是数字且数字整数的位数和小数的位数必须在指定范围内

    @Future

    日期必须在当前日期的未来

    @Past

    日期必须在当前日期的过去

    @Max

    最大不得超过此最大值

    @Min

    最大不得小于此最小值

    @NotNull

    不能为null,可以是空

    @Null

    必须为null

    @Pattern

    必须满足指定的正则表达式

    @Size

    集合、数组、map等的size()值必须在指定范围内

    @Email

    必须是email格式

    @Length

    长度必须在指定范围内

    @NotBlank

    字符串不能为null,字符串trim()后也不能等于“”

    @NotEmpty

    不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于“”

    @Range

    值必须在指定范围内

    @URL

    必须是一个URL

    注:此表格只是简单的对注解功能的说明,并没有对每一个注解的属性进行说明;可详见源码。

    下面简单测试一下上述各注解:

    测试所用模型为:

     

    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;

    }

    }

    测试方法为:

     

    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());

    }

    }

     

    }


    @Validated的使用时机

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

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

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

    以下是简单的测试代码:

     

    import com.aspire.model.ValidationBeanModel;

    import org.springframework.validation.annotation.Validated;

    import org.springframework.web.bind.annotation.RequestMapping;

    import org.springframework.web.bind.annotation.RestController;

     

    import javax.validation.constraints.DecimalMax;

     

    /**

    * Controller层 --- 初步简单测试 @Validated 的使用位置

    *

    * 对比测试过程:

    * 方案一 : 不在类上加@Validated注解,访问这六个接口

    * 方案二 : 在类上加@Validated注解,再次访问这六个接口

    *

    * 对比方案一和方案二,可初步得出@Validated的使用时机:

    * 1.当我们是在模型里面对模型字段添加约束注解,在Controller中使用模型接收数

    * 据时,@Validated要直接放在该模型参数前才有效。 如: "/test/one"

    * 2.当我们是直接在Controller层中的参数前,使用约束注解时,@Validated要直接放在类上,

    * 才会有效。如: /test/six

    *

    *

    * @author JustryDeng

    * @date 2019/1/18 22:22

    */

    @RestController

    @Validated

    public class JustryDengController {

     

     

    @RequestMapping(value = "/test/one")

    public String validatioOne(@Validated ValidationBeanModel.AbcDecimalMax myDecimalMax) {

    System.out.println(myDecimalMax.getMyDecimalMax());

    return "one pass!";

    }

     

    @RequestMapping(value = "/test/two")

    @Validated

    public String validatioTwo(ValidationBeanModel.AbcDecimalMax myDecimalMax) {

    System.out.println(myDecimalMax.getMyDecimalMax());

    return "two pass!";

    }

     

    @RequestMapping(value = "/test/three")

    public String validatioThree(ValidationBeanModel.AbcDecimalMax myDecimalMax) {

    System.out.println(myDecimalMax.getMyDecimalMax());

    return "three pass!";

    }

     

    @RequestMapping(value = "/test/four")

    public String validatioFour(@Validated @DecimalMax(value = "12.3") String myDecimalMax) {

    System.out.println(myDecimalMax);

    return "four pass!";

    }

     

    @RequestMapping(value = "/test/five")

    @Validated

    public String validatioFive(@DecimalMax(value = "12.3") String myDecimalMax) {

    System.out.println(myDecimalMax);

    return "five pass!";

    }

     

    @RequestMapping(value = "/test/six")

    @Validated

    public String validatioSix(@DecimalMax(value = "12.3") String myDecimalMax) {

    System.out.println(myDecimalMax);

    return "six pass!";

    }

    }


    @Validated与@Valid的简单对比说明

            @Valid注解与@Validated注解功能大部分类似;两者的不同主要在于:@Valid属于javax下的,而@Validated属于spring下;@Valid支持嵌套校验、而@Validated不支持,@Validated支持分组,而@Valid不支持。笔者这里只简单介绍@Validated的使用时机。


    自定义注解

            虽然Bean Validation和Hibernate Validator已经提供了非常丰富的校验注解,但是在实际业务中,难免会碰到一些现有注解不足以校验的情况;这时,我们可以考虑自定义Validation注解。

    示例:

    第一步:创建自定义注解

     

    import com.aspire.constraints.impl.JustryDengValidator;

     

    import javax.validation.Constraint;

    import javax.validation.Payload;

    import java.lang.annotation.Documented;

    import java.lang.annotation.Retention;

    import java.lang.annotation.Target;

     

    import static java.lang.annotation.ElementType.FIELD;

    import static java.lang.annotation.ElementType.PARAMETER;

    import static java.lang.annotation.RetentionPolicy.RUNTIME;

     

    /**

    * 自定义校验注解

    * 提示:

    * 1、message、contains、payload是必须要写的

    * 2、还需要什么方法可根据自己的实际业务需求,自行添加定义即可

    *

    * 注:当没有指定默认值时,那么在使用此注解时,就必须输入对应的属性值

    *

    * @author JustryDeng

    * @date 2019/1/15 1:17

    */

    @Target({FIELD, PARAMETER})

    @Retention(RUNTIME)

    @Documented

    // 指定此注解的实现,即:验证器

    @Constraint(validatedBy ={JustryDengValidator.class})

    public @interface ConstraintsJustryDeng {

     

    // 当验证不通过时的提示信息

    String message() default "JustryDeng : param value must contais specified value!";

     

    // 根据实际需求定的方法

    String contains() default "";

     

    // 约束注解在验证时所属的组别

    Class<?>[] groups() default { };

     

    // 负载

    Class<? extends Payload>[] payload() default { };

    }

    第二步:编写(第一步中的校验器实现类)该注解

     

    import com.aspire.constraints.anno.ConstraintsJustryDeng;

    import org.hibernate.validator.internal.engine.ValidationContext;

    import org.hibernate.validator.internal.engine.ValueContext;

    import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree;

     

    import javax.validation.ConstraintValidator;

    import javax.validation.ConstraintValidatorContext;

     

    /**

    * ConstraintsJustryDeng注解 校验器 实现

    * <p>

    * 注:验证器需要实现ConstraintValidator<U, V>, 其中 U为对应的注解类, V为被该注解标记的字段的类型(或其父类型)

    *

    * 注: 当项目启动后,会(懒加载)创建ConstraintValidator实例,在创建实例后会初始化调

    * 用{@link ConstraintValidator#initialize}方法。

    * 所以, 只有在第一次请求时,会走initialize方法, 后面的请求是不会走initialize方法的。

    *

    * 注: (懒加载)创建ConstraintValidator实例时, 会走缓存; 如果缓存中有,则直接使用相

    * 同的ConstraintValidator实例; 如果缓存中没有,那么会创建新的ConstraintValidator实例。

    * 由于缓存的key是能唯一定位的, 且 ConstraintValidator的实例属性只有在

    * {@link ConstraintValidator#initialize}方法中才会写;在{@link ConstraintValidator#isValid}

    * 方法中只是读。

    * 所以不用担心线程安全问题。

    *

    * 注: 如何创建ConstraintValidator实例的,可详见源码

    * @see ConstraintTree#getInitializedConstraintValidator(ValidationContext, ValueContext)

    *

    * @author JustryDeng

    * @date 2019/1/15 1:19

    */

    public class JustryDengValidator implements ConstraintValidator<ConstraintsJustryDeng, Object> {

     

    /** 错误提示信息 */

    private String contains;

     

    /**

    * 初始化方法, 在(懒加载)创建一个当前类实例后,会马上执行此方法

    *

    * 注: 此方法只会执行一次,即:创建实例后马上执行。

    *

    * @param constraintAnnotation

    * 注解信息模型,可以从该模型中获取注解类中定义的一些信息,如默认值等

    * @date 2019/1/19 11:27

    */

    @Override

    public void initialize(ConstraintsJustryDeng constraintAnnotation) {

    System.out.println(constraintAnnotation.message());

    this.contains = constraintAnnotation.contains();

    }

     

    /**

    * 校验方法, 每个需要校验的请求都会走这个方法

    *

    * 注: 此方法可能会并发执行,需要根据实际情况看否是需要保证线程安全。

    *

    * @param value

    * 被校验的对象

    * @param context

    * 上下文

    *

    * @return 校验是否通过

    */

    @Override

    public boolean isValid(Object value, ConstraintValidatorContext context) {

    if (value == null) {

    return false;

    }

    if (value instanceof String) {

    String strMessage = (String) value;

    return strMessage.contains(contains);

    } else if (value instanceof Integer) {

    return contains.contains(String.valueOf(value));

    }

    return false;

    }

     

    }

    第三步:自定义注解简单使用测试

    Controller层中是这样的:

    访问一下这个接口(当参数值符合要求时):

    访问一下这个接口(当参数值不符合要求时):


    对注解抛出的异常进行处理

    说明:当注解校验不通过时,直接将异常信息返回给前端其实并不友好,我们可以将异常包装一下,返回给前端。

    情况一:使用BindingResult类来容纳异常信息,当校验不通过时,不影响正常程                序往下走。我们只需要处理BindingResult中的异常信息即可。

    处理前:

    参数模型是这样的:

    Controller是这样的:

    使用postman测试,当校验不通过时显示如下:

    处理后:

    参数模型是这样的(没有变):

    Controller是这样的(在@Validated注解的参数后,紧接着加上BindingResult):

    再次使用postman测试,当校验不通过时显示如下:

    postman中返回了数据,说明虽然参数错误,但是不影响程序的执行。

    程序在控制台输出了如下信息:

    可见,后台已经获悉了错误,至于怎么处理,这就看伟大的程序员们了。

    情况二(推荐):通过SpringMVC全局异常处理器来处理异常。

    描述:如果不采用BindingResult来容纳异常信息时,那么异常会被向外抛出。注解校验不通过时,可能抛出的            异常有BindException异常、ValidationException异常(或其子类异常)、            MethodArgumentNotValidException异常。

    处理前:

    示例一:Controller和对应的参数模型是这样的:

    使用postman测试,当校验不通过时显示如下:

    示例二:Controller是这样的:

    使用postman测试,当校验不通过时显示如下:

    注:ConstraintViolationException异常是ViolationException异常的子异常。

    示例三:Controller是这样的:

    注:此处的User模型与情况一里给出的模型是一样的,这里就不再给出了。

    使用postman测试,当校验不通过时显示如下:

    进行处理:加入AOP全局异常处理器:

     

    import lombok.extern.slf4j.Slf4j;

    import org.springframework.http.HttpStatus;

    import org.springframework.validation.BindException;

    import org.springframework.validation.BindingResult;

    import org.springframework.validation.FieldError;

    import org.springframework.web.bind.MethodArgumentNotValidException;

    import org.springframework.web.bind.annotation.ExceptionHandler;

    import org.springframework.web.bind.annotation.ResponseStatus;

    import org.springframework.web.bind.annotation.RestControllerAdvice;

     

    import javax.validation.ConstraintViolationException;

    import javax.validation.ValidationException;

    import java.util.HashMap;

    import java.util.Map;

     

    /**

    * SpringMVC统一异常处理

    *

    * @author JustryDeng

    * @date 2019/10/12 16:28

    */

    @Slf4j

    @RestControllerAdvice

    public class GlobalExceptionHandler {

     

    /**

    * 处理Validated校验异常

    * <p>

    * 注: 常见的ConstraintViolationException异常, 也属于ValidationException异常

    *

    * @param e

    * 捕获到的异常

    * @return 返回给前端的data

    */

    @ResponseStatus(code = HttpStatus.BAD_REQUEST)

    @ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class})

    public Map<String, Object> handleParameterVerificationException(Exception e) {

    log.error(" handleParameterVerificationException has been invoked", e);

    Map<String, Object> resultMap = new HashMap<>(4);

    resultMap.put("code", "100001");

    String msg = null;

    /// BindException

    if (e instanceof BindException) {

    // getFieldError获取的是第一个不合法的参数(P.S.如果有多个参数不合法的话)

    FieldError fieldError = ((BindException) e).getFieldError();

    if (fieldError != null) {

    msg = fieldError.getDefaultMessage();

    }

    /// MethodArgumentNotValidException

    } else if (e instanceof MethodArgumentNotValidException) {

    BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();

    // getFieldError获取的是第一个不合法的参数(P.S.如果有多个参数不合法的话)

    FieldError fieldError = bindingResult.getFieldError();

    if (fieldError != null) {

    msg = fieldError.getDefaultMessage();

    }

    /// ValidationException 的子类异常ConstraintViolationException

    } else if (e instanceof ConstraintViolationException) {

    /*

    * ConstraintViolationException的e.getMessage()形如

    * {方法名}.{参数名}: {message}

    * 这里只需要取后面的message即可

    */

    msg = e.getMessage();

    if (msg != null) {

    int lastIndex = msg.lastIndexOf(':');

    if (lastIndex >= 0) {

    msg = msg.substring(lastIndex + 1).trim();

    }

    }

    /// ValidationException 的其它子类异常

    } else {

    msg = "处理参数时异常";

    }

    resultMap.put("msg", msg);

    return resultMap;

    }

     

    }

    处理后,使用postman再次进行上述两个请求:

    可见,异常处理成功!


    【补充】多级嵌套模型的校验

    假设:

    有api:

    有Company和Employee类:

    那么,当Department里面既有普通字段,又有类字段时:

    解决方式是,给复杂对象字段加上@Valid注解:


    【补充】groups分组校验

           在很多时候,同一个模型可能会在多处被用到,但每处的校验场景又不一定相同(如:新增用户接口、修改用户接口,参数都是User模型,在新增时User中name字段不能为空,userNo字段可以为空;在修改时User中name字段可以为空,userNo字段不能为空)。我们可以用groups来实现:同一个模型在不同场景下,(动态区分)校验模型中的不同字段。

    提示:实现groups功能的主要逻辑源码,可详见org.hibernate.validator.internal.engine.ValidatorImpl#validate。

    使用方式(示例说明)

    准备工作:自定义两个分组。

    提示:继承Default并不是必须的。只是说,如果继承了Default,那么@Validated(value = Create.class)的校验范畴就            为【Create】和【Default】;如果没继承Default,那么@Validated(value = Create.class)的校验范畴只            为【Create】,而@Validated(value = {Create.class, Default.class})的校验范畴才为【Create】和【Default】。            追注:原因可见下面的第二步中的说明。

    第一步:(在给模型里面的参数定义校验规则时,)给校验分配所属分组。

    注:Default组和无参构造机制类似,当没有指定分组时,会默认当前校验属于Default组,但是一旦主动给当前校验指定        了分组(如上图中的name字段,主动指定了属于Create组),那么就不会再额外指定属于Default组了。         追注:当然,也可以画蛇添足的主动指定所属分组为Default。

    第二步:启动校验时,指定校验(组别)范畴。

    为更直观的理解,再给出一张图:

    注:还有一个用于联合校验(如:字段A的校验,依赖于字段B的校验)等场景的注解javax.validation.GroupSequence        有时也非常实用,感兴趣的可自行了解。

    Processed: 0.011, SQL: 8