<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringBoot中處理校驗邏輯的兩種方式,真的很機智!

          共 6458字,需瀏覽 13分鐘

           ·

          2022-02-21 22:24

          大家好,我是二哥呀。最近正在開發(fā)一個知識庫學(xué)習(xí)網(wǎng)站編程貓,需要對請求參數(shù)進行校驗,比如說非空啊、長度限制啊等等,可選的解決方案有兩種:

          • 一種是用 Hibernate Validator 來處理
          • 一種是用全局異常來處理

          兩種方式,我們一一來實踐體驗一下。

          一、Hibernate Validator

          Spring Boot 已經(jīng)內(nèi)置了 Hibernate Validator 校驗框架,這個可以通過 Spring Boot 官網(wǎng)查看和確認。

          第一步,進入 Spring Boot 官網(wǎng),點擊 learn 這個面板,點擊參考文檔。

          第二步,在參考文檔頁點擊「依賴的版本」。

          第三步,在依賴版本頁就可以查看到所有的依賴了,包括版本號。

          PS:如果發(fā)現(xiàn)沒有起效,可能是依賴版本沖突了,手動把 Hibernate Validator 依賴添加到 pom.xml 文件就可以了。


          ????org.hibernate.validator
          ????hibernate-validator
          ????6.0.17.Final


          ????javax.validation
          ????validation-api
          ????2.0.1.Final

          通過 Hibernate Validator 校驗框架,我們可以直接在請求參數(shù)的字段上加入注解來完成校驗。

          具體該怎么做呢?

          第一步,在需要驗證的字段上加上 Hibernate Validator 提供的校驗注解。

          比如說我現(xiàn)在有一個用戶名和密碼登錄的請求參數(shù) UsersLoginParam 類:

          @Data
          @ApiModel(value="用戶登錄",?description="用戶表")
          public?class?UsersLoginParam?implements?Serializable?{
          ????private?static?final?long?serialVersionUID?=?1L;

          ????@ApiModelProperty(value?=?"登錄名")
          ????@NotBlank(message="登錄名不能為空")
          ????private?String?userLogin;

          ????@ApiModelProperty(value?=?"密碼")
          ????@NotBlank(message="密碼不能為空")
          ????private?String?userPass;
          }

          就可以通過 @NotBlank 注解來對用戶名和密碼進行判空校驗。除了 @NotBlank 注解,Hibernate Validator 還提供了以下常用注解:

          • @NotNull:被注解的字段不能為 null;
          • @NotEmpty:被注解的字段不能為空;
          • @Min:被注解的字段必須大于等于其value值;
          • @Max:被注解的字段必須小于等于其value值;
          • @Size:被注解的字段必須在其min和max值之間;
          • @Pattern:被注解的字段必須符合所定義的正則表達式;
          • @Email:被注解的字段必須符合郵箱格式。

          第二步,在對應(yīng)的請求接口(UsersController.login())中添加 @Validated 注解,并注入一個 BindingResult 參數(shù)。

          @Controller
          @Api(tags="用戶")
          @RequestMapping("/users")
          public?class?UsersController?{
          ????@Autowired
          ????private?IUsersService?usersService;

          ????@ApiOperation(value?=?"登錄以后返回token")
          ????@RequestMapping(value?=?"/login",?method?=?RequestMethod.POST)
          ????@ResponseBody
          ????public?ResultObject?login(@Validated?UsersLoginParam?users,?BindingResult?result)?{
          ????????String?token?=?usersService.login(users.getUserLogin(),?users.getUserPass());
          ????????if?(token?==?null)?{
          ????????????return?ResultObject.validateFailed("用戶名或密碼錯誤");
          ????????}
          ????????Map?tokenMap?=?new?HashMap<>();
          ????????tokenMap.put("token",?token);
          ????????tokenMap.put("tokenHead",?tokenHead);
          ????????return?ResultObject.success(tokenMap);
          ????}
          }

          第三步,為控制層(UsersController)創(chuàng)建一個切面,將通知注入到 BindingResult 對象中,然后再判斷是否有校驗錯誤,有錯誤的話返回校驗提示信息,否則放行。

          @Aspect
          @Component
          @Order(2)
          public?class?BindingResultAspect?{
          ????@Pointcut("execution(public?*?com.codingmore.controller.*.*(..))")
          ????public?void?BindingResult()?{
          ????}

          ????@Around("BindingResult()")
          ????public?Object?doAround(ProceedingJoinPoint?joinPoint)?throws?Throwable?{
          ????????Object[]?args?=?joinPoint.getArgs();
          ????????for?(Object?arg?:?args)?{
          ????????????if?(arg?instanceof?BindingResult)?{
          ????????????????BindingResult?result?=?(BindingResult)?arg;
          ????????????????if?(result.hasErrors())?{
          ????????????????????FieldError?fieldError?=?result.getFieldError();
          ????????????????????if(fieldError!=null){
          ????????????????????????return?ResultObject.validateFailed(fieldError.getDefaultMessage());
          ????????????????????}else{
          ????????????????????????return?ResultObject.validateFailed();
          ????????????????????}
          ????????????????}
          ????????????}
          ????????}
          ????????return?joinPoint.proceed();
          ????}
          }

          這里涉及到了 SpringBoot AOP 的知識,我在前面的文章里講解過了,戳這個鏈接可以直達:SpringBoot AOP 掃盲

          第四步,訪問登錄接口,用戶名和密碼都不傳入的情況下,就會返回“用戶名不能為空”的提示信息。

          通過 debug 的形式,體驗一下整個工作流程。

          可以看得出,Hibernate Validator 帶來的優(yōu)勢有這些:

          • 驗證邏輯與業(yè)務(wù)邏輯進行了分離,降低了程序耦合度;
          • 統(tǒng)一且規(guī)范的驗證方式,無需再次編寫重復(fù)的驗證代碼。

          不過,也帶來一些弊端,比如說:

          • 需要在請求接口的方法中注入 BindingResult 對象
          • 只能校驗一些非常簡單的邏輯,涉及到數(shù)據(jù)查詢就無能為力了。

          二、全局異常處理

          使用全局異常處理的優(yōu)點就是比較靈活,可以處理比較復(fù)雜的邏輯校驗,在校驗失敗的時候直接拋出異常,然后進行捕獲處理就可以了。

          第一步,新建一個自定義異常類 ApiException。

          public?class?ApiException?extends?RuntimeException?{
          ????private?IErrorCode?errorCode;

          ????public?ApiException(IErrorCode?errorCode)?{
          ????????super(errorCode.getMessage());
          ????????this.errorCode?=?errorCode;
          ????}

          ????public?ApiException(String?message)?{
          ????????super(message);
          ????}

          ????public?ApiException(Throwable?cause)?{
          ????????super(cause);
          ????}

          ????public?ApiException(String?message,?Throwable?cause)?{
          ????????super(message,?cause);
          ????}

          ????public?IErrorCode?getErrorCode()?{
          ????????return?errorCode;
          ????}
          }

          第二步,新建一個斷言處理類 Asserts,簡化拋出 ApiException 的步驟。

          public?class?Asserts?{
          ????public?static?void?fail(String?message)?{
          ????????throw?new?ApiException(message);
          ????}

          ????public?static?void?fail(IErrorCode?errorCode)?{
          ????????throw?new?ApiException(errorCode);
          ????}
          }

          第三步,新建一全局異常處理類 GlobalExceptionHandler,對異常信息進行解析,并封裝到統(tǒng)一的返回對象 ResultObject 中。

          @ControllerAdvice
          public?class?GlobalExceptionHandler?{
          ????@ResponseBody
          ????@ExceptionHandler(value?=?ApiException.class)
          ????public?ResultObject?handle(ApiException?e)?
          {
          ????????if?(e.getErrorCode()?!=?null)?{
          ????????????return?ResultObject.failed(e.getErrorCode());
          ????????}
          ????????return?ResultObject.failed(e.getMessage());
          ????}
          }

          全局異常處理類用到了兩個注解,@ControllerAdvice@ExceptionHandler

          @ControllerAdvice 是一個特殊的 @Component(可以通過源碼看得到),用于標(biāo)識一個類,這個類中被以下三種注解標(biāo)識的方法:@ExceptionHandler,@InitBinder,@ModelAttribute,將作用于所有@Controller 類的接口上。

          @Target({ElementType.TYPE})
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          @Component
          public?@interface?ControllerAdvice?{
          }

          @ExceptionHandler 注解的作用就是標(biāo)識統(tǒng)一異常處理,它可以指定要統(tǒng)一處理的異常類型,比如說我們自定義的 ApiException。

          第四步,在需要校驗的地方通過 Asserts 類拋出異常 ApiException。還拿用戶登錄這個接口來說明吧。

          @Controller
          @Api(tags="用戶")
          @RequestMapping("/users")
          public?class?UsersController?{
          ????@ApiOperation(value?=?"登錄以后返回token")
          ????@RequestMapping(value?=?"/login",?method?=?RequestMethod.POST)
          ????@ResponseBody
          ????public?ResultObject?login(@Validated?UsersLoginParam?users,?BindingResult?result)?{
          ????????String?token?=?usersService.login(users.getUserLogin(),?users.getUserPass());
          ?????
          ????????Map?tokenMap?=?new?HashMap<>();
          ????????tokenMap.put("token",?token);
          ????????tokenMap.put("tokenHead",?tokenHead);
          ????????return?ResultObject.success(tokenMap);
          ????}
          }

          該接口需要查詢數(shù)據(jù)庫驗證密碼是否正確,如果密碼不正確就拋出校驗信息“密碼不正確”。

          @Service
          public?class?UsersServiceImpl?extends?ServiceImpl<UsersMapper,?Users>?implements?IUsersService?{
          ????public?String?login(String?username,?String?password)?{
          ????????String?token?=?null;
          ????????//密碼需要客戶端加密后傳遞
          ????????UserDetails?userDetails?=?loadUserByUsername(username);
          ????????if?(!passwordEncoder.matches(password,?userDetails.getPassword()))?{
          ????????????Asserts.fail("密碼不正確");
          ?????????}
          ????????//?其他代碼省略
          ????????return?token;
          ????}
          }

          第五步,通過 ApiPost 來測試一下接口,故意把密碼輸錯。

          也可以通過 debug 的形式,體驗一下整個工作流程。

          三、總結(jié)

          實際開發(fā)中把兩者結(jié)合在一起用,就可以彌補彼此的短板了,簡單校驗用 Hibernate Validator,復(fù)雜一點的邏輯校驗,比如說需要數(shù)據(jù)庫查詢用全局異常處理來實現(xiàn)。

          源碼地址:https://github.com/itwanger/coding-more

          參考鏈接:http://www.macrozheng.com


          沒有什么使我停留——除了目的,縱然岸旁有玫瑰、有綠蔭、有寧靜的港灣,我是不系之舟

          推薦閱讀

          瀏覽 63
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久草青青在线 | 91成h| 麻豆国产91 在线播放猎赤 | 成人午夜福利视频 | 亚洲成人高清 |