1. 程式人生 > >springboot使用hibernate validator校驗

springboot使用hibernate validator校驗

配置 瀏覽器 .project else 12px 使用 很多 大於等於 ber

一、參數校驗

在開發中經常需要寫一些字段校驗的代碼,比如字段非空,字段長度限制,郵箱格式驗證等等,寫這些與業務邏輯關系不大的代碼個人感覺有兩個麻煩:

  • 驗證代碼繁瑣,重復勞動
  • 方法內代碼顯得冗長
  • 每次要看哪些參數驗證是否完整,需要去翻閱驗證邏輯代碼

hibernate validator(官方文檔)提供了一套比較完善、便捷的驗證實現方式。

spring-boot-starter-web包裏面有hibernate-validator包,不需要引用hibernate validator依賴。

二、hibernate validator校驗demo

先來看一個簡單的demo,添加了Validator的註解:

import org.hibernate.validator.constraints.NotBlank;

import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.Pattern;
技術分享圖片
@Getter
@Setter
@NoArgsConstructor
public class DemoModel {
    @NotBlank(message="用戶名不能為空")
    private String userName;

    @NotBlank(message="年齡不能為空")
    @Pattern(regexp="^[0-9]{1,2}$",message="年齡不正確")
    private String age;

    @AssertFalse(message = "必須為false")
    private Boolean isFalse;
    /**
     * 如果是空,則不校驗,如果不為空,則校驗
     */
    @Pattern(regexp="^[0-9]{4}-[0-9]{2}-[0-9]{2}$",message="出生日期格式不正確")
    private String birthday;
}
技術分享圖片

POST接口驗證,BindingResult是驗證不通過的結果集合:

技術分享圖片
    @RequestMapping("/demo2")
    public void demo2(@RequestBody @Valid DemoModel demo, BindingResult result){
        if(result.hasErrors()){
            for (ObjectError error : result.getAllErrors()) {
                System.out.println(error.getDefaultMessage());
            }
        }
    }
技術分享圖片

POST請求傳入的參數:{"userName":"dd","age":120,"isFalse":true,"birthday":"21010-21-12"}

輸出結果:

出生日期格式不正確
必須為false
年齡不正確

參數驗證非常方便,字段上註解+驗證不通過提示信息即可代替手寫一大堆的非空和字段限制驗證代碼。下面深入了解下參數校驗的玩法。

本文地址:http://www.cnblogs.com/mr-yang-localhost/p/7812038.html

三、hibernate的校驗模式

細心的讀者肯定發現了:上面例子中一次性返回了所有驗證不通過的集合,通常按順序驗證到第一個字段不符合驗證要求時,就可以直接拒絕請求了。Hibernate Validator有以下兩種驗證模式:

1、普通模式(默認是這個模式)

  普通模式(會校驗完所有的屬性,然後返回所有的驗證失敗信息)

2、快速失敗返回模式

  快速失敗返回模式(只要有一個驗證失敗,則返回)

兩種驗證模式配置方式:(參考官方文檔)

failFast:true 快速失敗返回模式 false 普通模式

ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.failFast( true )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

和 (hibernate.validator.fail_fast:true 快速失敗返回模式 false 普通模式)

ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

四、hibernate的兩種校驗

配置hibernate Validator為快速失敗返回模式:

技術分享圖片
@Configuration
public class ValidatorConfiguration {
    @Bean
    public Validator validator(){
        ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
                .configure()
                .addProperty( "hibernate.validator.fail_fast", "true" )
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();

        return validator;
    }
}
技術分享圖片

1、請求參數校驗

如demo裏示例的,驗證請求參數時,在@RequestBody DemoModel demo之間加註解 @Valid,然後後面加BindindResult即可;多個參數的,可以加多個@Valid和BindingResult,如:

public void test()(@RequestBody @Valid DemoModel demo, BindingResult result)

public void test()(@RequestBody @Valid DemoModel demo, BindingResult result,@RequestBody @Valid DemoModel demo2, BindingResult result2)

技術分享圖片
    @RequestMapping("/demo2")
    public void demo2(@RequestBody @Valid DemoModel demo, BindingResult result){
        if(result.hasErrors()){
            for (ObjectError error : result.getAllErrors()) {
                System.out.println(error.getDefaultMessage());
            }
        }
    }
技術分享圖片

2、GET參數校驗(@RequestParam參數校驗)

使用校驗bean的方式,沒有辦法校驗RequestParam的內容,一般在處理Get請求(或參數比較少)的時候,會使用下面這樣的代碼:

    @RequestMapping(value = "/demo3", method = RequestMethod.GET)
    public void demo3(@RequestParam(name = "grade", required = true) int grade,@RequestParam(name = "classroom", required = true) int classroom) {
        System.out.println(grade + "," + classroom);
    }

使用@Valid註解,對RequestParam對應的參數進行註解,是無效的,需要使用@Validated註解來使得驗證生效。如下所示:

a.此時需要使用MethodValidationPostProcessor 的Bean:

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
     /**默認是普通模式,會返回所有的驗證不通過信息集合*/ return new MethodValidationPostProcessor(); }

或 可對MethodValidationPostProcessor 進行設置Validator(因為此時不是用的Validator進行驗證,Validator的配置不起作用)

技術分享圖片
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
     /**設置validator模式為快速失敗返回*/ postProcessor.setValidator(validator()); return postProcessor; } @Bean public Validator validator(){ ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .addProperty( "hibernate.validator.fail_fast", "true" ) .buildValidatorFactory(); Validator validator = validatorFactory.getValidator(); return validator; }
技術分享圖片

b.方法所在的Controller上加註解@Validated

技術分享圖片
@RequestMapping("/validation")
@RestController
@Validated
public class ValidationController {
    /**如果只有少數對象,直接把參數寫到Controller層,然後在Controller層進行驗證就可以了。*/
    @RequestMapping(value = "/demo3", method = RequestMethod.GET)
    public void demo3(@Range(min = 1, max = 9, message = "年級只能從1-9")
                      @RequestParam(name = "grade", required = true)
                      int grade,
                      @Min(value = 1, message = "班級最小只能1")
                      @Max(value = 99, message = "班級最大只能99")
                      @RequestParam(name = "classroom", required = true)
                      int classroom) {
        System.out.println(grade + "," + classroom);
    }
}
技術分享圖片

c.返回驗證信息提示

可以看到:驗證不通過時,拋出了ConstraintViolationException異常,使用同一捕獲異常處理:

技術分享圖片
@ControllerAdvice
@Component
public class GlobalExceptionHandler {

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String handle(ValidationException exception) {
        if(exception instanceof ConstraintViolationException){
            ConstraintViolationException exs = (ConstraintViolationException) exception;

            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            for (ConstraintViolation<?> item : violations) {
          /**打印驗證不通過的信息*/ System.out.println(item.getMessage()); } } return "bad request, " ; } }
技術分享圖片

d.驗證

瀏覽器服務請求地址:http://localhost:8080/validation/demo3?grade=18&classroom=888

沒有配置快速失敗返回的MethodValidationPostProcessor 時輸出信息如下:

年級只能從1-9
班級最大只能99

配置了快速失敗返回的MethodValidationPostProcessor 時輸出信息如下:

年級只能從1-9

瀏覽器服務請求地址:http://localhost:8080/validation/demo3?grade=0&classroom=0

沒有配置快速失敗返回的MethodValidationPostProcessor 時輸出信息如下:

年級只能從1-9
班級最小只能1

配置了快速失敗返回的MethodValidationPostProcessor 時輸出信息如下:

年級只能從1-9

3、model校驗

待校驗的model:

技術分享圖片
@Data
public class Demo2 {
    @Length(min = 5, max = 17, message = "length長度在[5,17]之間")
    private String length;

    /**@Size不能驗證Integer,適用於String, Collection, Map and arrays*/
    @Size(min = 1, max = 3, message = "size在[1,3]之間")
    private String age;

    @Range(min = 150, max = 250, message = "range在[150,250]之間")
    private int high;

    @Size(min = 3,max = 5,message = "list的Size在[3,5]")
    private List<String> list;
}
技術分享圖片

驗證model,以下全部驗證通過:

技術分享圖片
    @Autowired
    private Validator validator;
    
    @RequestMapping("/demo3")
    public void demo3(){
        Demo2 demo2 = new Demo2();
        demo2.setAge("111");
        demo2.setHigh(150);
        demo2.setLength("ABCDE");
        demo2.setList(new ArrayList<String>(){{add("111");add("222");add("333");}});
        Set<ConstraintViolation<Demo2>> violationSet = validator.validate(demo2);
        for (ConstraintViolation<Demo2> model : violationSet) {
            System.out.println(model.getMessage());
        }
    }
技術分享圖片

4、對象級聯校驗

對象內部包含另一個對象作為屬性,屬性上加@Valid,可以驗證作為屬性的對象內部的驗證:(驗證Demo2示例時,可以驗證Demo2的字段)

技術分享圖片
@Data
public class Demo2 {
    @Size(min = 3,max = 5,message = "list的Size在[3,5]")
    private List<String> list;

    @NotNull
    @Valid
    private Demo3 demo3;
}

@Data
public class Demo3 {
    @Length(min = 5, max = 17, message = "length長度在[5,17]之間")
    private String extField;
}
技術分享圖片
級聯校驗:
技術分享圖片
    /**前面配置了快速失敗返回的Bean*/
    @Autowired
    private Validator validator;

    @RequestMapping("/demo3")
    public void demo3(){
        Demo2 demo2 = new Demo2();
        demo2.setList(new ArrayList<String>(){{add("111");add("222");add("333");}});

        Demo3 demo3 = new Demo3();
        demo3.setExtField("22");
        demo2.setDemo3(demo3);
        Set<ConstraintViolation<Demo2>> violationSet = validator.validate(demo2);
        for (ConstraintViolation<Demo2> model : violationSet) {
            System.out.println(model.getMessage());
        }
    }
技術分享圖片
可以校驗Demo3的extField字段。

5、分組校驗

結論:分組順序校驗時,按指定的分組先後順序進行驗證,前面的驗證不通過,後面的分組就不行驗證。

有這樣一種場景,新增用戶信息的時候,不需要驗證userId(因為系統生成);修改的時候需要驗證userId,這時候可用用戶到validator的分組驗證功能。

設置validator為普通驗證模式("hibernate.validator.fail_fast", "false"),用到的驗證GroupA、GroupB和model:

GroupA、GroupB:

public interface GroupA {
}

public interface GroupB {
}

驗證model:Person

技術分享圖片 View Code

如上Person所示,3個分組分別驗證字段如下:

  • GroupA驗證字段userId;
  • GroupB驗證字段userName、sex;
  • Default驗證字段age(Default是Validator自帶的默認分組)

a、分組

只驗證GroupA、GroupB標記的分組:

技術分享圖片
@RequestMapping("/demo5")
public void demo5(){
Person p = new Person();
/**GroupA驗證不通過*/
p.setUserId(-12);
/**GroupA驗證通過*/
//p.setUserId(12);
p.setUserName("a");
p.setAge(110);
p.setSex(5);
Set<ConstraintViolation<Person>> validate = validator.validate(p, GroupA.class, GroupB.class);
for (ConstraintViolation<Person> item : validate) {
System.out.println(item);
}
}
技術分享圖片

技術分享圖片
    @RequestMapping("/demo6")
    public void demo6(@Validated({GroupA.class, GroupB.class}) Person p, BindingResult result){
        if(result.hasErrors()){
            List<ObjectError> allErrors = result.getAllErrors();
            for (ObjectError error : allErrors) {
                System.out.println(error);
            }
        }
    }
技術分享圖片

GroupA、GroupB、Default都驗證不通過的情況:

驗證信息如下所示:

ConstraintViolationImpl{interpolatedMessage=‘必須在[4,20]‘, propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate=‘必須在[4,20]‘}
ConstraintViolationImpl{interpolatedMessage=‘必須大於0‘, propertyPath=userId, rootBeanClass=class validator.demo.project.model.Person, messageTemplate=‘必須大於0‘}
ConstraintViolationImpl{interpolatedMessage=‘性別必須在[0,2]‘, propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate=‘性別必須在[0,2]‘}

GroupA驗證通過、GroupB、Default驗證不通過的情況:

驗證信息如下所示:

ConstraintViolationImpl{interpolatedMessage=‘必須在[4,20]‘, propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate=‘必須在[4,20]‘}
ConstraintViolationImpl{interpolatedMessage=‘性別必須在[0,2]‘, propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate=‘性別必須在[0,2]‘}

b、組序列

除了按組指定是否驗證之外,還可以指定組的驗證順序,前面組驗證不通過的,後面組不進行驗證:

指定組的序列(GroupA》GroupB》Default):

@GroupSequence({GroupA.class, GroupB.class, Default.class})
public interface GroupOrder {
}

測試demo:

技術分享圖片
    @RequestMapping("/demo7")
    public void demo7(){
        Person p = new Person();
        /**GroupA驗證不通過*/
        //p.setUserId(-12);
        /**GroupA驗證通過*/
        p.setUserId(12);
        p.setUserName("a");
        p.setAge(110);
        p.setSex(5);
        Set<ConstraintViolation<Person>> validate = validator.validate(p, GroupOrder.class);
        for (ConstraintViolation<Person> item : validate) {
            System.out.println(item);
        }
    }
技術分享圖片

技術分享圖片
    @RequestMapping("/demo8")
    public void demo8(@Validated({GroupOrder.class}) Person p, BindingResult result){
        if(result.hasErrors()){
            List<ObjectError> allErrors = result.getAllErrors();
            for (ObjectError error : allErrors) {
                System.out.println(error);
            }
        }
    }
技術分享圖片

GroupA、GroupB、Default都驗證不通過的情況:

驗證信息如下所示:

ConstraintViolationImpl{interpolatedMessage=‘必須大於0‘, propertyPath=userId, rootBeanClass=class validator.demo.project.model.Person, messageTemplate=‘必須大於0‘}

GroupA驗證通過、GroupB、Default驗證不通過的情況:

驗證信息如下所示:

ConstraintViolationImpl{interpolatedMessage=‘必須在[4,20]‘, propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate=‘必須在[4,20]‘}
ConstraintViolationImpl{interpolatedMessage=‘性別必須在[0,2]‘, propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate=‘性別必須在[0,2]‘}

結論:分組順序校驗時,按指定的分組先後順序進行驗證,前面的驗證不通過,後面的分組就不行驗證。

五、自定義驗證器

一般情況,自定義驗證可以解決很多問題。但也有無法滿足情況的時候,此時,我們可以實現validator的接口,自定義自己需要的驗證器。

如下所示,實現了一個自定義的大小寫驗證器:

技術分享圖片
public enum CaseMode {
    UPPER,
    LOWER;
}


@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface CheckCase {
    String message() default "";

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

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

    CaseMode value();
}


public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
    private CaseMode caseMode;
    public void initialize(CheckCase checkCase) {
        this.caseMode = checkCase.value();
    }

    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        if (s == null) {
            return true;
        }

        if (caseMode == CaseMode.UPPER) {
            return s.equals(s.toUpperCase());
        } else {
            return s.equals(s.toLowerCase());
        }
    }
}
技術分享圖片

要驗證的Model:

技術分享圖片
    public class Demo{
        @CheckCase(value = CaseMode.LOWER,message = "userName必須是小寫")
        private String userName;

        public String getUserName() {
            return userName;
        }

        public void setUserName(String userName) {
            this.userName = userName;
        }
    }
技術分享圖片

validator配置:

技術分享圖片
    @Bean
    public Validator validator(){
        ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
                .configure()
                .addProperty( "hibernate.validator.fail_fast", "true" )
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();

        return validator;
    }
技術分享圖片

驗證測試:

技術分享圖片
    @RequestMapping("/demo4")
    public void demo4(){
        Demo demo = new Demo();
        demo.setUserName("userName");
        Set<ConstraintViolation<Demo>> validate = validator.validate(demo);
        for (ConstraintViolation<Demo> dem : validate) {
            System.out.println(dem.getMessage());
        }
    }
技術分享圖片

輸出結果:

userName必須是小寫

六、常見的註解

Bean Validation 中內置的 constraint
@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(regex=,flag=) 被註釋的元素必須符合指定的正則表達式
Hibernate Validator 附加的 constraint
@NotBlank(message =) 驗證字符串非null,且長度必須大於0
@Email 被註釋的元素必須是電子郵箱地址
@Length(min=,max=) 被註釋的字符串的大小必須在指定的範圍內
@NotEmpty 被註釋的字符串的必須非空
@Range(min=,max=,message=) 被註釋的元素必須在合適的範圍內

//大於0.01,不包含0.01
@NotNull
@DecimalMin(value = "0.01", inclusive = false)
private Integer greaterThan;

//大於等於0.01
@NotNull
@DecimalMin(value = "0.01", inclusive = true)
private BigDecimal greatOrEqualThan;

@Length(min = 1, max = 20, message = "message不能為空")
//不能將Length錯用成Range
//@Range(min = 1, max = 20, message = "message不能為空")
private String message;

七、參考資料

參考資料:

  • http://docs.jboss.org/hibernate/validator/4.2/reference/zh-CN/html_single/#validator-gettingstarted

springboot使用hibernate validator校驗