當前位置:
首頁 > 知識 > SpringBoot | 第八章:統一異常、數據校驗處理

SpringBoot | 第八章:統一異常、數據校驗處理

(點擊

上方公眾號

,可快速關注)




來源:oKong ,


blog.lqdev.cn/2018/07/20/springboot/chapter-eight/




前言



在web應用中,請求處理時,出現異常是非常常見的。所以當應用出現各類異常時,進行異常的捕獲或者二次處理(比如sql異常正常是不能外拋)是非常必要的,比如在開發對外api服務時,約定了響應的參數格式,如respCode、respMsg,調用方根據錯誤碼進行自己的業務邏輯。本章節就重點講解下統一異常和數據校驗處理。




springboot中,默認在發送異常時,會跳轉值/error請求進行錯誤的展現,根據不同的Content-Type展現不同的錯誤結果,如json請求時,直接返回json格式參數。




瀏覽器訪問異常時:







使用postman訪問時:






統一異常處理




顯然,默認的異常頁是對用戶或者調用者而言都是不友好的,所以一般上我們都會進行實現自己業務的異常提示信息。




創建全局的統一異常處理類




利用@ControllerAdvice和@ExceptionHandler定義一個統一異常處理類






  • @ControllerAdvice:控制器增強,使@ExceptionHandler、@InitBinder、@ModelAttribute註解的方法應用到所有的 @RequestMapping註解的方法。



  • @ExceptionHandler:異常處理器,此註解的作用是當出現其定義的異常時進行處理的方法




創建異常類:CommonExceptionHandler




@ControllerAdvice


public class CommonExceptionHandler {


 


    /**


     *  攔截Exception類的異常

     * @param e


     * @return


     */


    @ExceptionHandler(Exception.class)


    @ResponseBody

    public Map<String,Object> exceptionHandler(Exception e){


        Map<String,Object> result = new HashMap<String,Object>();


        result.put("respCode", "9999");


        result.put("respMsg", e.getMessage());


        //正常開發中,可創建一個統一響應實體,如CommonResp

        return result; 


    }


}




多餘不同異常(如自定義異常),需要進行不同的異常處理時,可編寫多個exceptionHandler方法,註解ExceptionHandler指定處理的異常類,如




/**


 * 攔截 CommonException 的異常


 * @param ex


 * @return


 */


@ExceptionHandler(CommonException.class)


@ResponseBody


public Map<String,Object> exceptionHandler(CommonException ex){


    log.info("CommonException:{}({})",ex.getMsg(), ex.getCode());


    Map<String,Object> result = new HashMap<String,Object>();


    result.put("respCode", ex.getCode());


    result.put("respMsg", ex.getMsg());


    return result; 


}




由於加入了@ResponseBody,所以返回的是json格式,







說明異常已經被攔截了。




可攔截不同的異常,進行不同的異常提示,比如NoHandlerFoundException、HttpMediaTypeNotSupportedException、AsyncRequestTimeoutException等等,這裡就不列舉了,讀者可自己加入後實際操作下。




對於返回頁面時,返回ModelAndView即可,如





@ExceptionHandler(value = Exception.class)


    public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {


        ModelAndView mav = new ModelAndView();


        mav.addObject("exception", e);


        mav.addObject("url", req.getRequestURL());


        mav.setViewName(DEFAULT_ERROR_VIEW);


        return mav;


    }




由於工作中都是才有前後端分離開發模式,所以一般上都沒有直接返回資源頁的需求了,一般上都是返回固定的響應格式,如respCode、respMsg、data,前端通過判斷respCode的值進行業務判斷,是彈窗還是跳轉頁面。




數據校驗




在web開發時,對於請求參數,一般上都需要進行參數合法性校驗的,原先的寫法時一個個欄位一個個去判斷,這種方式太不通用了,所以java的JSR 303: Bean Validation規範就是解決這個問題的。




JSR 303隻是個規範,並沒有具體的實現,目前通常都是才有hibernate-validator進行統一參數校驗。




JSR303定義的校驗類型







Hibernate Validator 附加的 constraint







創建實體類





@Data


@NoArgsConstructor


@AllArgsConstructor


public class DemoReq {


     


    @NotBlank(message="code不能為空")


    String code;


     


    @Length(max=10,message="name長度不能超過10")


    String name;


}




然後在控制層方法里,加入@Valid即可,這樣在訪問前,會對請求參數進行檢驗。





@GetMapping("/demo/valid")


public String demoValid(@Valid DemoReq req) {


    return req.getCode() + "," + req.getName();


}




啟動,後訪問http://127.0.0.1:8080/demo/valid







加上正確參數後,http://127.0.0.1:8080/demo/valid?code=3&name=s







這樣數據的統一校驗就完成了,對於其他註解的使用,大家可自行谷歌下,基本上都很簡單的,對於已有的註解無法滿足校驗需要時,也可進行自定義註解的開發,一下簡單講解下,自定義註解的編寫




不使用@valid的情況下,也可利用編程的方式編寫一個工具類,進行實體參數校驗





public class ValidatorUtil {


    private static Validator validator = ((HibernateValidatorConfiguration) Validation


            .byProvider(HibernateValidator.class).configure()).failFast(true).buildValidatorFactory().getValidator();


 


    /**


     * 實體校驗


     * 


     * @param obj


     * @throws CommonException


     */


    public static <T> void validate(T obj) throws CommonException {


        Set<ConstraintViolation<T>> constraintViolations = validator.validate(obj, new Class[0]);


        if (constraintViolations.size() > 0) {


            ConstraintViolation<T> validateInfo = (ConstraintViolation<T>) constraintViolations.iterator().next();


            // validateInfo.getMessage() 校驗不通過時的信息,即message對應的值


            throw new CommonException("0001", validateInfo.getMessage());


        }


    }


}




使用





@GetMapping("/demo/valid")


public String demoValid(@Valid DemoReq req) {


    //手動校驗


    ValidatorUtil.validate(req);


    return req.getCode() + "," + req.getName();


}




自定義校驗註解




自定義註解,主要時實現ConstraintValidator的處理類即可,這裡已編寫一個校驗常量的註解為例:參數值只能為特定的值。




自定義註解





@Documented


//指定註解的處理類


@Constraint(validatedBy = {ConstantValidatorHandler.class })


@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })


@Retention(RUNTIME)


public @interface Constant {


 


   String message() default "{constraint.default.const.message}";


 


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


 


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


 


   String value();


}




註解處理類





public class ConstantValidatorHandler implements ConstraintValidator<Constant, String> {


 


    private String constant;


 


    @Override


    public void initialize(Constant constraintAnnotation) {


        //獲取設置的欄位值


        this.constant = constraintAnnotation.value();


    }


 


    @Override


    public boolean isValid(String value, ConstraintValidatorContext context) {


        //判斷參數是否等於設置的欄位值,返回結果


        return constant.equals(value);


    }


}




使用





@Constant(message = "verson只能為1.0",value="1.0")


String version;




運行:





此時,自定義註解已生效。大家可根據實際需求進行開發。




大家看到在校驗不通過時,返回的異常信息是不友好的,此時可利用統一異常處理,對校驗異常進行特殊處理,特別說明下,對於異常處理類,共有以下幾種情況(被@RequestBody和@RequestParam註解的請求實體,校驗異常類是不同的)





@ExceptionHandler(MethodArgumentNotValidException.class)


    public Map<String,Object> handleBindException(MethodArgumentNotValidException ex) {


        FieldError fieldError = ex.getBindingResult().getFieldError();


        log.info("參數校驗異常:{}({})", fieldError.getDefaultMessage(),fieldError.getField());


        Map<String,Object> result = new HashMap<String,Object>();


        result.put("respCode", "01002");


        result.put("respMsg", fieldError.getDefaultMessage());


        return result;


    }


 


 


    @ExceptionHandler(BindException.class)


    public Map<String,Object> handleBindException(BindException ex) {


        //校驗 除了 requestbody 註解方式的參數校驗 對應的 bindingresult 為 BeanPropertyBindingResult


        FieldError fieldError = ex.getBindingResult().getFieldError();


        log.info("必填校驗異常:{}({})", fieldError.getDefaultMessage(),fieldError.getField());


        Map<String,Object> result = new HashMap<String,Object>();


        result.put("respCode", "01002");


        result.put("respMsg", fieldError.getDefaultMessage());


        return result;


    }




啟動後,提示就友好了







所以統一異常還是很有必要的。




總結




本章節主要是闡述了統一異常處理和數據的合法性校驗,同時簡單實現了一個自定義的註解類,大家在碰見已有註解無法解決時,可通過自定義的形式進行,當然對於通用而已,利用@Pattern(正則表達式)基本上都是可以實現的。




最後




目前互聯網上很多大佬都有springboot系列教程,如有雷同,請多多包涵了。本文是作者在電腦前一字一句敲的,每一步都是實踐的。若文中有所錯誤之處,還望提出,謝謝。




系列






  • SpringBoot | 第一章:第一個 SpringBoot 應用



  • SpringBoot | 第二章:lombok 介紹及簡單使用



  • SpringBoot | 第三章:springboot 配置詳解



  • SpringBoot | 第四章:日誌管理



  • SpringBoot | 第五章:多環境配置



  • SpringBoot | 第六章:常用註解介紹及簡單使用



  • SpringBoot | 第七章:過濾器、監聽器、攔截器




【關於投稿】




如果大家有原創好文投稿,請直接給公號發送留言。




① 留言格式:


【投稿】+《 文章標題》+ 文章鏈接

② 示例:


【投稿】《不要自稱是程序員,我十多年的 IT 職場總結》:http://blog.jobbole.com/94148/

③ 最後請附上您的個人簡介哈~






看完本文有收穫?請轉發分享給更多人


關注「ImportNew」,提升Java技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 ImportNew 的精彩文章:

趣圖:有那麼難么?不很簡單嘛?
使用 JITWatch 查看 JVM 的 JIT 編譯代碼

TAG:ImportNew |