歡迎光臨
每天分享高質量文章

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 exceptionHandler(Exception e){

        Map result = new HashMap();

        result.put(“respCode”, “9999”);

        result.put(“respMsg”, e.getMessage());

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

        return result; 

    }

}

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

/**

 * 攔截 CommonException 的異常

 * @param ex

 * @return

 */

@ExceptionHandler(CommonException.class)

@ResponseBody

public Map exceptionHandler(CommonException ex){

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

    Map result = new HashMap();

    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 void validate(T obj) throws CommonException {

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

        if (constraintViolations.size() > 0) {

            ConstraintViolation validateInfo = (ConstraintViolation) 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 {

 

    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 handleBindException(MethodArgumentNotValidException ex) {

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

        log.info(“引數校驗異常:{}({})”, fieldError.getDefaultMessage(),fieldError.getField());

        Map result = new HashMap();

        result.put(“respCode”, “01002”);

        result.put(“respMsg”, fieldError.getDefaultMessage());

        return result;

    }

 

 

    @ExceptionHandler(BindException.class)

    public Map handleBindException(BindException ex) {

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

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

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

        Map result = new HashMap();

        result.put(“respCode”, “01002”);

        result.put(“respMsg”, fieldError.getDefaultMessage());

        return result;

    }

啟動後,提示就友好了

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

總結

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

最後

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

系列


【關於投稿】


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


① 留言格式:
【投稿】+《 文章標題》+ 文章連結

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

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



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

關註「ImportNew」,提升Java技能

贊(0)

分享創造快樂