1. 程式人生 > >SpringMvc資料校驗@Valid等註解的使用與工具類抽取

SpringMvc資料校驗@Valid等註解的使用與工具類抽取

最近在重構老專案的程式碼,發現校驗入參佔用了很多程式碼,之前我對這一塊的認識侷限於使用StringUtils等工具來多個if塊進行判斷,程式碼是沒什麼問題,但是總寫這些令人生煩,畢竟寫程式碼也要講究優雅的嘛,於是呢我就研究了一下JavaEE Api 上的校驗類,基本上推翻了我之前對校驗註解之類的認識,在這裡記錄一下所得。

  1. @NotNull @NotBlank @NotEmpty 這三個註解的區別以及使用

    @NotNull:校驗入參不能為空,無法正確校檢長度為0的字串或以完全為空格的字串

    @NotBlank: 包含@NotNull的功能,可以校驗字串內容是否為空

    @NotEmpty: 校驗傳入集合是否為空

    當以上幾個註解校驗不被滿足的時候,就會丟擲異常,打印出預設的訊息內容,

    age not be null這樣的錯誤資訊

    想自定義錯誤資訊可以如:@NotNull(message = "資訊不能為空")來定義

  2. @Valid的使用

    如果入參為一個物件,想校驗這個物件內容是否正確的時候,會用到1節中所講的幾個註解,

    但是此時會有一個問題:如果不使用@Valid註解,1節中的註解在入參過程中是不生效的,只有儲存的時候才會被校驗(不排除PO和DTO是同一個物件的情況)

    當然,這有一個大前提,就是沒有其它對入參校驗的方法執行。

    **所以如果是簡單為了讓異常報出來而不是直接返回的時候可以只用@Valid

  3. 自定義校驗與錯誤資訊的處理

    (1)使用BindingResult物件接收被@Valid校驗不通過的錯誤資訊

        /**
         * 使用BindingResult與@Valid配合的方式
         * 拿到錯誤資訊,帶部分堆疊
         */
        @PostMapping("/validationTest6")
        public String validationTest6(@RequestBody @Valid User user, 
                                      BindingResult result){
            if(result.hasErrors()){
                //取一條錯誤資訊
                ObjectError next = result.getAllErrors().iterator().next();
                log.error("error={}", next);
                //後邊可以自己返回錯誤資訊也可以自定義
                return next.toString();
            }
            //do somethings
            return "校驗通過";
        }
    
        /**
         * 使用BindingResult與@Valid配合的方式
         * 只拿到錯誤資訊
         */
        @PostMapping("/validationTest7")
        public String validationTest7(@RequestBody @Valid User user, 
                                      BindingResult result){
            if(result.hasErrors()){
                //取一條錯誤資訊
                ObjectError next = result.getAllErrors().iterator().next();
                String defaultMessage = next.getDefaultMessage();
                log.error("error={}", defaultMessage);
                //後邊可以自己返回錯誤資訊也可以自定義
                return defaultMessage;
            }
            //do somethings
            return "校驗通過";
        }

    (2)使用Validator物件,不信賴於@Valid的實現

        /**
         * 使用Validator未抽取工具類時的實現
         */
        @PostMapping("/validationTest9")
        public String validationTest9(@RequestBody User user){
            ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
            Validator validator = factory.getValidator();
            Set<ConstraintViolation<User>> validate = validator.validate(user);
            if(!validate.isEmpty()){
                ConstraintViolation<User> next = validate.iterator().next();
                String message = next.getMessage();
                log.error("error={}",message);
                return message;
            }
            //do somethings
            return "校驗通過";
        }

    上邊給大家介紹了兩種方法,其中第二種看起來程式碼更多一些,其實這應該就是沒有使用@Valid的問題了,因為你並不知道@Valid註解校驗錯誤程式碼有多少。效率基本差不多少

    下邊為大家提供一個工具類,分別提供了上邊兩種方式的封裝,如果只想使用BindingResult方式,那麼大可以去除用不到的程式碼

  4. 封裝的工具類與使用

    (1)工具類程式碼

    package com.cnblogs.hellxz.myutils;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.ObjectError;
    
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    import javax.validation.Validator;
    import javax.validation.ValidatorFactory;
    import java.util.Set;
    
    /**
     * <b>類名</b>: ValidatorUtils
     * <p><b>描    述</b> 校驗入參 </p>
     *
     * <p><b>建立日期</b>: 8/24/18 12:54 PM </p>
     *
     * @author HELLXZ 張
     * @version 1.0
     * @since jdk 1.8
     */
    public class ValidatorUtils {
    
        private static final Logger log = LoggerFactory.getLogger(ValidatorUtils.class);
        private static final Validator validator;
    
        static {
            ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
            validator = factory.getValidator();
        }
    
        /**
         * 分組進行校驗或完整校驗
         * 區別在於是否傳groups類
         * ps:分組的好處在於同一個DTO分別給不同的方法指定不同的校驗引數時候非常有用
         *      也就不用再去重寫一個新的相同欄位的DTO指定不同校驗欄位的操作,更靈活
         * @param object 被校驗的DTO物件
         * @param groups 分組類,可以是介面也可以是類,僅作為標識
         * @return 想要返回的結果
         */
        public static <T> String validEntity(final T object, Class... groups) {
            Set<ConstraintViolation<T>> violations = validator.validate(object, groups);
            if (!violations.isEmpty()) {
                //這裡只取第一條錯誤,防止返回引數過多
                ConstraintViolation<T> violation = violations.iterator().next();
                log.error(violation.getMessage());
                //下面的程式碼可以使用公司或個人習慣的返回工具類也可以
                return "{\"code\":\"400\",\"msg\":\"" + violation.getMessage() + "\"}";
            }
            return null;
        }
    
        /**
         * 校驗入參物件的某個屬性是否滿足要求,跳過其它校驗, 支援分組
         * @param object 被校驗的DTO物件
         * @param propertyName DTO物件內的屬性名
         * @param groups 分組名
         * @return 校驗出錯返回的結果
         */
        public static <T> String validProperty(final T object, String propertyName, 
                                               Class... groups) {
            Set<ConstraintViolation<T>> violations = validator.validateProperty(object, propertyName, groups);
            if (!violations.isEmpty()) {
                //這裡只取第一條錯誤,防止返回引數過多
                ConstraintViolation<T> violation = violations.iterator().next();
                log.error(violation.getMessage());
                //下面的程式碼可以使用公司或個人習慣的返回工具類也可以
                return "{\"code\":\"400\",\"msg\":\"" + 
                    violation.getMessage() + "\"}";
            }
            return null;
        }
    
        /**
         * 使用BindingResult與@Valid註解一起使用實現的工具類
         * @param result BindingResult物件
         * @return 結果串
         */
        public static String validEntity(BindingResult result){
            if(result.hasErrors()){
                //取一條錯誤資訊
                ObjectError next = result.getAllErrors().iterator().next();
                String defaultMessage = next.getDefaultMessage();
                log.error("error={}", defaultMessage);
                //後邊可以自己返回錯誤資訊也可以自定義
                return "{\"code\":\"400\",\"msg\":\"" + defaultMessage + "\"}";
            }
            return null;
        }
    }

    (2) 工具類的使用

        private static Logger log = 
            LoggerFactory.getLogger(ValidateController.class);
    
        /**
         * 檢查User物件註解在A組內的校驗
         */
        @PostMapping("/validationTest1")
        public String validationTest1(@RequestBody User user){
            String valid = ValidatorUtils.validEntity(user, A.class);
            if(null != valid) return valid;
            //do somethings
            return "校驗通過";
        }
    
        /**
         * 檢查User物件註解在B組內的校驗
         */
        @PostMapping("/validationTest2")
        public String validationTest2(@RequestBody User user){
            String valid = ValidatorUtils.validEntity(user, B.class);
            if(null != valid) return valid;
            //do somethings
            return "校驗通過";
        }
    
        /**
         * 檢查User物件註解在A組和B組內的校驗
         * ps: 是A組內校驗加上B組內校驗,不是校驗同時在兩個組!
         */
        @PostMapping("/validationTest3")
        public String validationTest3(@RequestBody User user){
            String valid = ValidatorUtils.validEntity(user, A.class, B.class);
            if(null != valid) return valid;
            //do somethings
            return "校驗通過";
        }
    
        /**
         * 校驗入參物件中指定欄位
         * 其中分組可以不傳,如果傳的話,請注意該欄位必須在該組下,否則不會被校驗
         */
        @PostMapping("/validationTest4")
        public String validationTest4(@RequestBody User user){
            String valid = ValidatorUtils.validProperty(user,"age", B.class);
            if(null != valid) return valid;
            //do somethings
            return "校驗通過";
        }
    
        /**
         * 驗證上邊Test4的說法
         */
        @PostMapping("/validationTest5")
        public String validationTest5(@RequestBody User user){
            String valid = ValidatorUtils.validProperty(user,"age", A.class);
            if(null != valid) return valid;
            //do somethings
            return "校驗通過";
        }
    
        /**
         * 使用方法7抽的工具類
         */
        @PostMapping("/validationTest8")
        public String validationTest8(@RequestBody @Valid User user, 
                                      BindingResult result){
            String errorMsg = ValidatorUtils.validEntity(result);
            if(StringUtils.isNotBlank(errorMsg)) return errorMsg;
            //do somethings
            return "校驗通過";
        }

    (3) 補充:User類

    public class User {
    
        @NotBlank(message = "使用者名稱不能為空串",groups = A.class)
        private String username;
        @NotNull(message = "年齡不能為空",groups = B.class)
        private String age;
        @NotBlank(message = "身高不能為空", groups = {A.class, B.class})
        private String height;
        @NotEmpty(message = "孩子列表不能為空")
        private List<Object> childs;
    
        @Email(message = "email不正確")
        private String email;
    
        //省略get set 方法
    }
  5. 擴充套件部分

    空檢查

    @Null:驗證某引數必須為空 ps: 沒有過 =. = ,其餘的空檢查在上邊已經講了

    長度檢查

    @Size(min=, max=): 驗證物件(Array,Collection,Map,String)長度是否在給定的範圍之內

    @Length(min=, max=): 長度是否在範圍內

    Booelan檢查

    @AssertTrue: 驗證 Boolean 物件是否為 true

    @AssertFalse: 驗證 Boolean 物件是否為 false

    數值檢查:建議用在基礎型別包裝型別上和String型別上

    @Min: 驗證最小值

    @Max: 驗證最大值

    @DecimalMax:小數數值不能大於設定的值,小數存在精度

    @DecimalMin:小數數值不能小於設定的值,小數存在精度

    @Digits: 驗證數字和字串組成是否合法

    範圍檢查

    @Range(min=, max=):驗證物件最小與最大的範圍

    號碼檢查

    @CreditCardNumber: 信用卡驗證(hibernate的實現)

    @Email: 驗證是否是郵件地址,如果為null,則不進行驗證,通過驗證(validation和hibernate均有實現)

    日期檢查

    @Past:驗證 Date 和 Calendar 物件是否在當前時間之前,null 被認為是通過驗證

    @Future:驗證 Date 和 Calendar 物件是否在當前時間之後 ,null 被認為是通過驗證

    正則表示式檢查

    @Pattern(regexp = ""): 使用正則表示式驗證String,接受字元序列,regexp必填,所修飾為null時認為是通過驗證

  6. 終了

    憋了一晚上才想通校驗的實際用法,參考了一些的博文,把最大部分的內容引用粘在下面了,基本是官方文件和一個外文網站

    https://docs.oracle.com/javaee/7/api/toc.htm

    https://docs.jboss.org/hibernate/stable/validator/api/

    https://www.baeldung.com/javax-validation

    https://blog.csdn.net/yanfeng918/article/details/42618593

    本文程式碼:https://github.com/HellxZ/MyUtils.git

    宣告:本文內容未經許可可以轉載,但請註明出處

相關推薦

SpringMvc資料@Valid註解的使用工具抽取

最近在重構老專案的程式碼,發現校驗入參佔用了很多程式碼,之前我對這一塊的認識侷限於使用StringUtils等工具來多個if塊進行判斷,程式碼是沒什麼問題,但是總寫這些令人生煩,畢竟寫程式碼也要講究優雅的嘛,於是呢我就研究了一下JavaEE Api 上的校驗類,基本上推翻了我之前對校驗註解之類的認識,在這裡記

SpringMVC資料註解

@AssertFalse 被註解的元素必須為false @AssertTrue 被註解的元素必須為false @DecimalMax(value) 被註解的元素必須為一個數字,其值必須小於等於指定的最小值 @DecimalMin(Value) 被註解的元素必須為一個數字,其值必須大於等於指定的最小值 @Dig

springmvc 資料

private Integer id; @NotEmpty private String lastName; @Email private String email; //1 male, 0 female private Integer gend

SpringMVC資料、檔案上傳

package com.remoa.user.domain; import java.io.Serializable; import java.util.ArrayList; import java.util.List; public class UserVOExample implements Seri

Spring Boot 資料@Valid+統一異常處理

1.先在你需要校驗的實體類上面加上所需要的註解 為了測試,我自己就簡單寫了。@NotNull 和 @NotBlank 不能為空 @Entity @Table(name = "User") @Data public class User implement

從深處去掌握資料@Valid的作用(級聯校

每篇一句 NBA裡有兩大笑話:一是科比沒天賦,二是詹姆斯沒技術 相關閱讀 【小家Java】深入瞭解資料校驗:Java Bean Validation 2.0(JSR303、JSR349、JSR380)Hibernate-Validation 6.x使用案例 【小家Spring】讓Controller支援對

springmvc中後端@Valid註解

@Valid註解用於校驗,所屬包為:javax.validation.Valid。① 首先需要在實體類的相應欄位上新增用於充當校驗條件的註解,如:@Min,如下程式碼(age屬於Girl類中的屬性):@Min(value = 20,message = "結婚年齡限制")  p

SpringMVC-8 資料型別轉換、資料格式化資料

1. 資料繫結流程   SpringMVC通過反射機制對目標處理方法進行解析,將請求訊息繫結到處理方法的入參中。其中,資料繫結的核心部件是DataBinder,執行機制如下:      資料繫結的具體流程說明如下: SpringMVC主框架將S

SpringBoot全域性異常資料

異常處理是每個專案中都繞不開的話題,那麼如何優雅的處理異常,是本文的話題。本文將結合SpringBoot框架一起和大家探討下。 要思考的問題 在現在的前後端互動中,通常都規範了介面返回方式,如返回的介面狀態(成功|失敗)以及要返回的資料在那個欄位取,或者說失敗了以後提示資訊從介面哪裡返回,因

SpringMVC總結--資料格式轉換和資料

SpringMVC資料繫結流程        A:SpingMVC 主框架將 ServletRequest 物件及目標方法的入參例項傳遞 WebDataBinderFactory 例項,以建立DataBinder 例項物件     &nbs

如何優雅的處理異常?SpringBoot全域性異常資料

要思考的問題 在現在的前後端互動中,通常都規範了介面返回方式,如返回的介面狀態(成功|失敗)以及要返回的資料在那個欄位取,或者說失敗了以後提示資訊從介面哪裡返回,因此,如果想做全域性異常,並且異常發生後能準確的返回給前端解析,那麼需要異常發生時返回給前端的格式與正常失敗場景的格式一致。

SpringMVC資料轉換、格式化和資料

目錄 5.案例 一、資料轉換 Spring MVC 上下文中內建了很多轉換器,可完成大多數 Java 型別的轉換工作。 1.ConversionService ConversionServ

SpringMVC資料(一)

什麼是資料校驗 這個比較好理解,就是用來驗證客戶輸入的資料是否合法,比如客戶登入時,使用者名稱不能為空,或者不能超出指定長度等要求,這就叫做資料校驗。資料校驗分為客戶端校驗和服務端校驗 客戶端校驗:js校驗 服務端校驗:springmvc使用validat

SpringMVC學習06】SpringMVC中的資料

  這一篇博文主要總結一下springmvc中對資料的校驗。在實際中,通常使用較多是前端的校驗,比如頁面中js校驗,對於安全要求較高的建議在服務端也要進行校驗。服務端校驗可以是在控制層conroller

CRC碼生成資料原始碼程式 (包括CRC-4,5,6,7,8,16,32)

C程式碼 收藏程式碼 /******************************************************************** * Name: CRC-4/ITU x4+x+1 *

資料陣列磁碟

海明校驗碼和 異或校驗是兩種最為常用的 資料校驗演算法。海明校驗碼是由理查德.海明提出的,不僅能檢測錯誤,還能給出錯誤位置並自動糾正。海明校驗的基本思想是: 將有效資訊按照某種規律分成若干組,對每一個組作奇偶測試並安排一個校驗位,從而能提供多位檢錯資訊,以定位錯誤點並糾正

全量資料同步資料實踐——應對百億量級分庫分表異構庫遷移

在一家發展中的公司搬磚,正好遇到分庫分表,資料遷移的需求比較多,就入坑了。最近有個系統重構,一直做資料重構、遷移、校驗等工作,基本能覆蓋資料遷移的各個基本點,所以趁機整理一下。 資料同步的場景是:資料庫拆分、資料冗餘、資料表重構。 資料重構服務主要包括:全量

SpringMVC學習(三)———— springmvc資料的實現

一、什麼是資料校驗?       這個比較好理解,就是用來驗證客戶輸入的資料是否合法,比如客戶登入時,使用者名稱不能為空,或者不能超出指定長度等要求,這就叫做資料校驗。       資料校驗分為客戶端校驗和服務端校驗         客戶端校驗:js校驗         服務端校驗:springmvc

springmvc 使用JSR-303進行資料

專案中,通常使用較多的是前端的校驗,比如頁面中js校驗以及form表單使用bootstrap校驗。然而對於安全要求較高點建議在服務端進行校驗。 服務端校驗: 控制層controller:校驗頁面請求的引數的合法性。在服務端控制層controller校驗

SpringMVC實現頁面和java模型的資料互動以及檔案上傳下載和資料

1. 專案結構 2.  springMVC-servlet.xml 配置檔案 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org