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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM