1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        SpringBoot 使用 Validation API 和 全局異常 優(yōu)雅的校驗方法參數(shù)

        共 24491字,需瀏覽 49分鐘

         ·

        2021-07-09 14:55

        公眾號關(guān)注 “GitHub今日熱榜
        設(shè)為 “星標”,帶你挖掘更多開發(fā)神器!







        一、為什么使用 Validation 來驗證參數(shù) 


        通常我們在使用spring框架編寫接口時,對于部分接口的參數(shù)我們要進行判空或者格式校驗來避免程序出現(xiàn)異常。那是我們一般都是使用if-else逐個對參數(shù)進行校驗。這種方法按邏輯來說也是沒有問題的,同樣也能實現(xiàn)預(yù)期效果。但是,這樣的代碼從可讀性以及美觀程序來看,是非常糟糕的。那么,我們就可以使用@valid注解來幫助我們優(yōu)雅的校驗參數(shù)。


        二、如何使用Validation相關(guān)注解進行參數(shù)校驗

          

        ①為實體類中的參數(shù)或者對象添加相應(yīng)的注解;②在控制器層進行注解聲明,或者手動調(diào)用校驗方法進行校驗;③對異常進行處理;


        三、Validation類的相關(guān)注解及描述


        驗證注解驗證的數(shù)據(jù)類型說明
        @AssertFalseBoolean,boolean驗證注解的元素值是false
        @AssertTrueBoolean,boolean驗證注解的元素值是true
        @NotNull任意類型驗證注解的元素值不是null
        @Null任意類型驗證注解的元素值是null
        @Min(value=值)BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存儲的是數(shù)字)子類型驗證注解的元素值大于等于@Min指定的value值
        @Max(value=值)和@Min要求一樣驗證注解的元素值小于等于@Max指定的value值
        @DecimalMin(value=值)和@Min要求一樣驗證注解的元素值大于等于@ DecimalMin指定的value值
        @DecimalMax(value=值)和@Min要求一樣驗證注解的元素值小于等于@ DecimalMax指定的value值
        @Digits(integer=整數(shù)位數(shù), fraction=小數(shù)位數(shù))和@Min要求一樣驗證注解的元素值的整數(shù)位數(shù)和小數(shù)位數(shù)上限
        @Size(min=下限, max=上限)字符串、Collection、Map、數(shù)組等驗證注解的元素值的在min和max(包含)指定區(qū)間之內(nèi),如字符長度、集合大小
        @Pastjava.util.Date,java.util.Calendar;Joda Time類庫的日期類型驗證注解的元素值(日期類型)比當前時間早
        @Future與@Past要求一樣驗證注解的元素值(日期類型)比當前時間晚
        @NotBlankCharSequence子類型驗證注解的元素值不為空(不為null、去除首位空格后長度為0),不同于@NotEmpty,@NotBlank只應(yīng)用于字符串且在比較時會去除字符串的首位空格
        @Length(min=下限, max=上限)CharSequence子類型驗證注解的元素值長度在min和max區(qū)間內(nèi)
        @NotEmptyCharSequence子類型、Collection、Map、數(shù)組驗證注解的元素值不為null且不為空(字符串長度不為0、集合大小不為0)
        @Range(min=最小值, max=最大值)BigDecimal,BigInteger,CharSequence, byte, short, int, long等原子類型和包裝類型驗證注解的元素值在最小值和最大值之間
        @Email(regexp=正則表達式,flag=標志的模式)CharSequence子類型(如String)驗證注解的元素值是Email,也可以通過regexp和flag指定自定義的email格式
        @Pattern(regexp=正則表達式,flag=標志的模式)String,任何CharSequence的子類型驗證注解的元素值與指定的正則表達式匹配
        @Valid任何非原子類型指定遞歸驗證關(guān)聯(lián)的對象如用戶對象中有個地址對象屬性,如果想在驗證用戶對象時一起驗證地址對象的話,在地址對象上加@Valid注解即可級聯(lián)驗證


        此處只列出Validator提供的大部分驗證約束注解,請參考hibernate validator官方文檔了解其他驗證約束注解和進行自定義的驗證約束注解定義。


        四、使用 Validation API 進行參數(shù)效驗步驟


        整個過程如下圖所示,用戶訪問接口,然后進行參數(shù)效驗。對于GET請求的參數(shù)可以使用@validated注解配合上面相應(yīng)的注解進行校驗或者按照原先if-else方式進行效驗。而對于POST請求,大部分是以表單數(shù)據(jù)即以實體對象為參數(shù),可以使用@Valid注解方式進行效驗。如果效驗通過,則進入業(yè)務(wù)邏輯,否則拋出異常,交由全局異常處理器進行處理。



        五、 Spring Validation的三種校驗方式


        第一種:在Controller方法參數(shù)前加@Valid注解——校驗不通過時直接拋異常,get請求直接在平面參數(shù)前添加相應(yīng)的校驗規(guī)則注解,使用這種的話一般結(jié)合統(tǒng)一異常處理進行處理;


        第二種:在Controller方法參數(shù)前加@Valid注解,參數(shù)后面定義一個BindingResult類型參數(shù)——執(zhí)行時會將校驗結(jié)果放進bindingResult里面,用戶自行判斷并處理。


        /**
          * 將校驗結(jié)果放進BindingResult里面,用戶自行判斷并處理
          *
          * @param userInfo
          * @param bindingResult
          * @return
          */

         
         @PostMapping("/testBindingResult")
         
         public String testBindingResult(@RequestBody @Valid UserInfo userInfo, BindingResult bindingResult) {
         
             // 參數(shù)校驗
         
             if (bindingResult.hasErrors()) {
         
                 String messages = bindingResult.getAllErrors()
         
                         .stream()
         
                         .map(ObjectError::getDefaultMessage)
         
                         .reduce((m1, m2) -> m1 + ";" + m2)
         
                         .orElse("參數(shù)輸入有誤!");
         
                 //這里可以拋出自定義異常,或者進行其他操作
         
                 throw new IllegalArgumentException(messages);
         
             }
         
             return "操作成功!";
         
         }


        這里我們是直接拋出了異常,如果沒有進行全局異常處理的話,接口將會返回如下信息:



        第三種:用戶手動調(diào)用對應(yīng)API執(zhí)行校驗——Validation.buildDefault ValidatorFactory().getValidator().validate(xxx)


        這種方法適用于校驗任意一個有valid注解的實體類,并不僅僅是只能校驗接口中的參數(shù);


        這里我提取出一個工具類,如下:


        import org.springframework.util.CollectionUtils;
        import javax.validation.ConstraintViolation;
        import javax.validation.Valid;
        import javax.validation.Validation;
        import java.util.Set;
         
         
         
        /**
         * 手動調(diào)用api方法校驗對象
         */

         
        public class MyValidationUtils {
         
            public static void validate(@Valid Object user) {
         
                Set<ConstraintViolation<@Valid Object>> validateSet = Validation.buildDefaultValidatorFactory()
                        .getValidator()
                        .validate(user, new Class[0]);
                if (!CollectionUtils.isEmpty(validateSet)) {
                    String messages = validateSet.stream()
                            .map(ConstraintViolation::getMessage)
                            .reduce((m1, m2) -> m1 + ";" + m2)
                            .orElse("參數(shù)輸入有誤!");
                    throw new IllegalArgumentException(messages);
                }
            }
        }


        六、springboot項目中實戰(zhàn)演練


        spring-boot-starter-web依賴已經(jīng)集成相關(guān)jar,無需額外引入。


        1.對實體類的變量進行注解標注


        實體類中添加 @Valid 相關(guān)驗證注解,并在注解中添加出錯時的響應(yīng)消息。


        User.class


        import org.hibernate.validator.constraints.Length;
         
        import javax.validation.Valid;
        import javax.validation.constraints.NotBlank;
        import javax.validation.constraints.NotNull;
        import javax.validation.constraints.Pattern;
         
         
        @Data
        public class User {
         
            @NotBlank(message = "姓名不能為空")
            private String username;
         
            @NotBlank(message = "密碼不能為空")
            @Length(min = 6, max = 16, message = "密碼長度為6-16位")
            private String password;
         
            @Pattern(regexp = "0?(13|14|15|17|18|19)[0-9]{9}", message = "手機號格式不正確")
            private String phone;
         
            // 嵌套必須加 @Valid,否則嵌套中的驗證不生效
         
            @Valid
            @NotNull(message = "userinfo不能為空")
            private UserInfo userInfo;
         
        }


        如果是嵌套的實體對象,并且也要校驗該對象,則需要在最外層屬性上添加 @Valid 注解。


        UserInfo.class


        import javax.validation.constraints.Max;
        import javax.validation.constraints.NotBlank;
         
        @Data
         
        public class UserInfo {
         
            @NotBlank(message = "年齡不為空")
            @Max(value = 18, message = "不能超過18歲")
            private String age;
         
            @NotBlank(message = "性別不能為空")
            private String gender;
         
        }


        2.創(chuàng)建自定義異常


        自定義異常類,方便我們處理手動拋出的異常。


        public class ParamaErrorException extends RuntimeException {
            public ParamaErrorException() {
            }
            public ParamaErrorException(String message) {
                super(message);
            }
        }


        3.自定義響應(yīng)枚舉類


        定義一個返回信息的枚舉類,方便我們快速響應(yīng)信息,不必每次都寫返回消息和響應(yīng)碼。


        public enum ResultEnum {
         
            SUCCESS(1000, "請求成功"),
            PARAMETER_ERROR(1001, "請求參數(shù)有誤!"),
            UNKNOWN_ERROR(9999, "未知的錯誤!");
         
            private Integer code;
            private String message;
            ResultEnum(Integer code, String message) {
                this.code = code;
                this.message = message;
            }
            public Integer getCode() {
                return code;
            }
            public String getMessage() {
                return message;
            }
        }


        4.自定義響應(yīng)對象類


        創(chuàng)建用于返回調(diào)用方的響應(yīng)信息的實體類。


        import com.sue.demo.enums.ResultEnum;
        import lombok.Data;
         
        @Data
         
        public class ResponseResult {
            private Integer code;
            private String msg;
            public ResponseResult() {
            }
            public ResponseResult(ResultEnum resultEnum) {
                this.code = resultEnum.getCode();
                this.msg = resultEnum.getMessage();
            }
         
            public ResponseResult(Integer code, String msg) {
                this.code = code;
                this.msg = msg;
            }
         
        }


        5.添加全局異常處理


        全局異常用于處理校驗不通過時拋出的異常,并通過接口返回,同時對其他未知異常進行處理。


        import com.sue.demo.controller.ResponseResult;
        import com.sue.demo.enums.ResultEnum;
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
        import org.springframework.http.HttpStatus;
        import org.springframework.http.converter.HttpMessageNotReadableException;
        import org.springframework.util.StringUtils;
        import org.springframework.validation.BindingResult;
        import org.springframework.validation.FieldError;
        import org.springframework.validation.ObjectError;
        import org.springframework.web.bind.MethodArgumentNotValidException;
        import org.springframework.web.bind.MissingServletRequestParameterException;
        import org.springframework.web.bind.annotation.ExceptionHandler;
        import org.springframework.web.bind.annotation.ResponseStatus;
        import org.springframework.web.bind.annotation.RestControllerAdvice;
         
        import java.util.List;
         
        @RestControllerAdvice("com.sue.demo.controller")
        public class GlobalExceptionHandler {
            private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
            /**
             * 忽略參數(shù)異常處理器
             * @param e 忽略參數(shù)異常
             * @return ResponseResult
         
             */

         
            @ResponseStatus(HttpStatus.BAD_REQUEST)
            @ExceptionHandler(MissingServletRequestParameterException.class)
            public ResponseResult parameterMissingExceptionHandler(MissingServletRequestParameterException e) {
                logger.error("", e);
                return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), "請求參數(shù) " + e.getParameterName() + " 不能為空");
            }
         
            /**
             * 缺少請求體異常處理器
             * @param e 缺少請求體異常
             * @return ResponseResult
             */

         
            @ResponseStatus(HttpStatus.BAD_REQUEST)
            @ExceptionHandler(HttpMessageNotReadableException.class)
            public ResponseResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
                logger.error("", e);
                return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), "參數(shù)體不能為空");
            }
         
            /**
             * 參數(shù)效驗異常處理器
             * @param e 參數(shù)驗證異常
             * @return ResponseInfo
             */

         
            @ResponseStatus(HttpStatus.BAD_REQUEST)
            @ExceptionHandler(MethodArgumentNotValidException.class)
            public ResponseResult parameterExceptionHandler(MethodArgumentNotValidException e) {
                logger.error("", e);
                // 獲取異常信息
                BindingResult exceptions = e.getBindingResult();
                // 判斷異常中是否有錯誤信息,如果存在就使用異常中的消息,否則使用默認消息
                if (exceptions.hasErrors()) {
                    List<ObjectError> errors = exceptions.getAllErrors();
                    if (!errors.isEmpty()) {
                        // 這里列出了全部錯誤參數(shù),按正常邏輯,只需要第一條錯誤即可
                        FieldError fieldError = (FieldError) errors.get(0);
                        return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), fieldError.getDefaultMessage());
                    }
                }
                return new ResponseResult(ResultEnum.PARAMETER_ERROR);
            }
         
            /**
             * 自定義參數(shù)錯誤異常處理器
             * @param e 自定義參數(shù)
             * @return ResponseInfo
             */

         
            @ResponseStatus(HttpStatus.BAD_REQUEST)
            @ExceptionHandler({ParamaErrorException.class})
            public ResponseResult paramExceptionHandler(ParamaErrorException e) {
                logger.error("", e);
                // 判斷異常中是否有錯誤信息,如果存在就使用異常中的消息,否則使用默認消息
                if (!StringUtils.isEmpty(e.getMessage())) {
                    return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), e.getMessage());
                }
                return new ResponseResult(ResultEnum.PARAMETER_ERROR);
            }
         
         
            /**
             * 其他異常
             * @param e
             * @return
             */

         
            @ResponseStatus(HttpStatus.BAD_REQUEST)
            @ExceptionHandler({Exception.class})
            public ResponseResult otherExceptionHandler(Exception e) {
                logger.error("其他異常", e);
                // 判斷異常中是否有錯誤信息,如果存在就使用異常中的消息,否則使用默認消息
                if (!StringUtils.isEmpty(e.getMessage())) {
                    return new ResponseResult(ResultEnum.UNKNOWN_ERROR.getCode(), e.getMessage());
                }
                return new ResponseResult(ResultEnum.UNKNOWN_ERROR);
            }
        }


        6.接口類中添加相關(guān)注解


        處理get請求直接在參數(shù)前添加驗證注解,處理post請求時在對象前添加@Valid注解


        TestController.class


        import com.sue.demo.entity.User;
        import com.sue.demo.entity.UserInfo;
        import com.sue.demo.enums.ResultEnum;
        import com.sue.demo.exception.ParamaErrorException;
        import com.sue.demo.util.MyValidationUtils;
        import com.sue.demo.util.ResponseResult;
        import io.swagger.annotations.Api;
        import io.swagger.annotations.ApiOperation;
        import org.hibernate.validator.constraints.Length;
        import org.springframework.validation.BindingResult;
        import org.springframework.validation.ObjectError;
        import org.springframework.validation.annotation.Validated;
        import org.springframework.web.bind.annotation.GetMapping;
        import org.springframework.web.bind.annotation.PostMapping;
        import org.springframework.web.bind.annotation.RequestBody;
        import org.springframework.web.bind.annotation.RequestParam;
        import org.springframework.web.bind.annotation.RestController;
         
         
        import javax.validation.Valid;
        import java.util.List;
         
         
        @Validated
        @RestController
        @Api(value = "測試使用validation驗證參數(shù)")
        public class TestController {
         
            /**
             * 測試get方法,手動if進行判空,校驗失敗時手動拋出自定義異常
             * @param username 姓名
             * @return ResponseResult
             */

         
            @ApiOperation(value = "測試get方法", notes = "輸入用戶名")
            @GetMapping("/testGet")
            public ResponseResult testGet(String username) {
                if (username == null || "".equals(username)) {
                    throw new ParamaErrorException("username 不能為空");
                }
                return new ResponseResult(ResultEnum.SUCCESS);
            }
         
            /**
             * 使用注解校驗get請求平面參數(shù),需要在Controller類頭部添加@Validated注解,否則不能成功校驗,這種方法不用手動拋出異常
             * @param username
             * @return
             */

         
            @ApiOperation(value = "測試get方法", notes = "輸入用戶名")
            @GetMapping("/testGetByValidated")
            public ResponseResult testGetByValidated(@Length(max = 4) @RequestParam("username") String username) {
                return new ResponseResult(ResultEnum.SUCCESS);
            }
         
            /**
             * post方法傳入單個對象進行校驗,在參數(shù)前添加@Valid注解,校驗失敗時會拋出異常并使用全局異常進行處理
             * @param userInfo 用戶信息
             * @return ResponseResult
             */

         
            @ApiOperation(value = "post方法傳入單個對象", notes = "傳入json對象")
            @PostMapping("/testUserInfo")
            public ResponseResult testUserInfo(@Valid @RequestBody UserInfo userInfo) {
                return new ResponseResult(ResultEnum.SUCCESS);
            }
         
         
         
            /**
             * post方法傳入對象,手動校驗,此時參數(shù)前沒有添加@Valid注解,所以不會自動進行校驗,手動調(diào)用validate方法進行校驗,失敗時會拋出異常
             * @param userInfo
             * @return ResponseResult
             */

         
            @ApiOperation(value = "post方法傳入對象,手動測試", notes = "單個對象")
            @PostMapping("/checkByMethod")
            public ResponseResult checkByMethod(@RequestBody UserInfo userInfo) {
                //調(diào)用api校驗
                MyValidationUtils.validate(userInfo);
                return new ResponseResult(ResultEnum.SUCCESS);
            }
         
         
            /**
             * post方法傳入多個對象,當使用@Valid校驗對象集合時,要在控制層添加@Validated注解,否則不會對集合中的每個對象進行校驗
             * @param userInfo
             * @return ResponseResult
             */

         
            @ApiOperation(value = "post方法傳入多個對象", notes = "多個對象")
            @PostMapping("/testUserList")
            public ResponseResult testUserList(@Valid @RequestBody List<UserInfo> userInfo) {
                return new ResponseResult(ResultEnum.SUCCESS);
            }
         
         
         
            /**
             * 測試對象中嵌套對象的情況,此時也要在對象屬性上添加@Valid注解
             * @param user
             * @return
             */

         
            @ApiOperation(value = "測試對象中嵌套對象的情況")
            @PostMapping("/checkUser")
            public ResponseResult checkUser(@Valid @RequestBody User user) {
                return new ResponseResult(ResultEnum.SUCCESS);
            }
         
         
         
            /**
             * 將校驗結(jié)果放進BindingResult里面,用戶自行判斷并處理
             * @param userInfo
             * @param bindingResult
             * @return
             */

         
            @PostMapping("/testBindingResult")
            public String testBindingResult(@RequestBody @Valid UserInfo userInfo, BindingResult bindingResult) {
                // 參數(shù)校驗
                if (bindingResult.hasErrors()) {
                    String messages = bindingResult.getAllErrors()
                            .stream()
                            .map(ObjectError::getDefaultMessage)
                            .reduce((m1, m2) -> m1 + ";" + m2)
                            .orElse("參數(shù)輸入有誤!");
                    //這里可以拋出自定義異常,或者進行其他操作
                    throw new IllegalArgumentException(messages);
                }
                return "操作成功!";
            }
        }


        7.進行測試


        補充:使用自定義參數(shù)注解


        1.我們這里創(chuàng)建一個身份證校驗注解


        @Documented
        @Target({ElementType.PARAMETER, ElementType.FIELD})
        @Retention(RetentionPolicy.RUNTIME)
        @Constraint(validatedBy = IdentityCardNumberValidator.class)
        public @interface IdentityCardNumber {
            String message() default "身份證號碼不合法";
            Class<?>[] groups() default {};
            Class<? extends Payload>[] payload() default {};
         
        }


        這個注解是作用在Field字段上,運行時生效,觸發(fā)的是IdentityCardNumber這個驗證類。


        • message 定制化的提示信息,主要是從ValidationMessages.properties里提取,也可以依據(jù)實際情況進行定制
        • groups 這里主要進行將validator進行分類,不同的類group中會執(zhí)行不同的validator操作
        • payload 主要是針對bean的,使用不多。


        2.自定義Validator


        import javax.validation.ConstraintValidator;
        import javax.validation.ConstraintValidatorContext;
         
        public class IdentityCardNumberValidator implements ConstraintValidator<IdentityCardNumber, Object> {
            @Override
            public void initialize(IdentityCardNumber identityCardNumber) {
            }
            @Override
            public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
                return IdCardValidatorUtils.isValidate18Idcard(o.toString());
            }
        }


        校驗工具類IdCardValidatorUtils.class


        3. 使用自定義的注解


        @NotBlank(message = "身份證號不能為空")
        @IdentityCardNumber(message = "身份證信息有誤,請核對后提交")
        private String clientCardNo;




        出處:csdn.net/chenyao1994/article/details/107858409










        關(guān)注GitHub今日熱榜,專注挖掘好用的開發(fā)工具,致力于分享優(yōu)質(zhì)高效的工具、資源、插件等,助力開發(fā)者成長!








        點個在看,你最好看


        瀏覽 57
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            男女a级片 | 日韩精品动漫一区二区三区 | 五级黄高潮片90分钟 | 国产精品36p | 操b在线免费观看 | 十八禁免费观看网站 | 国产熟妇毛多 A片欧美蜜臀 | 小静好湿好紧太爽视频 | 美女用手扒开自己下面 | 国产成人美女视频 |