SpringBoot 對 controller 層捕獲全局異常並處理的方法(@ControllerAdvice 和 @ExceptionHandler)

引言

在開發中,我們會有如下的場景:某個接口中,存在一些業務異常。例如用戶輸入的參數校驗失敗、用戶名密碼不存在等。當觸發這些業務異常時,我們需要拋出這些自定義的業務異常,並對其進行處理。一般我們要把這些異常信息的狀態碼和異常描述,友好地返回給調用者,調用者則利用狀態碼等信息判斷異常的具體情況。

過去,我們可能需要在 controller 層通過 try/catch 處理。首先 catch 自定義異常,然後 catch 其它異常。對於不同的異常,我們需要在 catch 的同時封裝將要返回的對象。然而,這麼做的弊端就是代碼會變得冗長。每個接口都需要做 try/catch 處理,而且一旦需要調整,所有的接口都需要修改一遍,非常不利於代碼的維護,如下段代碼所示

@RequestMapping (value = "/test")
public ResponseEntity test() {
    ResponseEntity re = new ResponseEntity();
    // 業務處理
    // ...
    try {
        // 業務
    } catch (BusinessException e) {
        logger.info("業務發生異常,code:" + e.getCode() + "msg:" + e.getMsg());
        re.setCode(e.getCode());
        re.setMsg(e.getMsg());
        return re;
    } catch (Exception e) {
        logger.error("服務錯誤:", e);
        re.setCode("xxxxx");
        re.setMsg("服務錯誤");
        return re;
    }
    return re;
}

那麼,有沒有什麼方法可以簡便地處理這些異常信息呢?答案是肯定的。Spring 3.2 中,新增了 @ControllerAdvice 註解,可以用於定義 @ExceptionHandler@InitBinder@ModelAttribute,並應用到所有 @RequestMapping 中。簡單來說就是,可以通過 @ControllerAdvice 註解配置一個全局異常處理類,來統一處理 controller 層中的異常,於此同時 controller 中可以不用再寫 try/catch,這使得代碼既整潔又便於維護。

使用方法

定義自定義異常

有關自定義異常相關知識點這裏就不詳細說明了,如果不瞭解的話自行搜索一下。這裏貼上一個簡單的自定義業務異常類。

/**
 * 自定義業務異常類
 *
 * @author Yuzhe Ma
 * @date 2018/11/28
 */
@Data
public class BusinessException extends RuntimeException {
    private String code;
    private String msg;

    public BusinessException(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

注:@Data 爲 Lombok 插件。自動生成 set/get 方法。具體使用方法這裏就不展開介紹了。

@ControllerAdvice+@ExceptionHand` 配置全局異常處理類

/**
 * 全局異常處理器
 *
 * @author Yuzhe Ma
 * @date 2018/11/12
 */
@ControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 處理 Exception 異常
     *
     * @param httpServletRequest httpServletRequest
     * @param e                  異常
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public ResponseEntity exceptionHandler(HttpServletRequest httpServletRequest, Exception e) {
        logger.error("服務錯誤:", e);
        return new ResponseEntity("xxx", "服務出錯");
    }

    /**
     * 處理 BusinessException 異常
     *
     * @param httpServletRequest httpServletRequest
     * @param e                  異常
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = BusinessException.class)
    public ResponseEntity businessExceptionHandler(HttpServletRequest httpServletRequest, BusinessException e) {
        logger.info("業務異常。code:" + e.getCode() + "msg:" + e.getMsg());
        return new ResponseEntity(e.getCode(), e.getMsg());
    }
}

@ControllerAdvice

定義該類爲全局異常處理類。

@ExceptionHandler

定義該方法爲異常處理方法。value 的值爲需要處理的異常類的 class 文件。在例子中,方法傳入兩個參數。一個是對應的 Exception 異常類,一個是 HttpServletRequest 類。當然,除了這兩種參數,還支持傳入一些其他參數。詳見文檔https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/ExceptionHandler.html

這樣,就可以對不同的異常進行統一處理了。通常,爲了使 controller 中不再使用任何 try/catch,也可以在 GlobalExceptionHandler 中對 Exception 做統一處理。這樣其他沒有用 @ExceptionHandler 配置的異常就都會統一被處理。

遇到異常時拋出異常即可

在業務中,遇到業務異常的地方,直接使用 throw 拋出對應的業務異常即可。例如

throw new BusinessException("3000", "賬戶密碼錯誤");

在 Controller 中的寫法

Controller 中,不需要再寫 try/catch,除非特殊用途。

@RequestMapping(value = "/test")
public ResponseEntity test() {
    ResponseEntity re = new ResponseEntity();
    // 業務處理
    // ...
    return re;
}

結果展示

異常拋出後,返回如下結果。

{
    "code": "3000",
    "msg": "賬戶密碼錯誤",
    "data": null
}

注意

  1. 不一定必須在 controller 層本身拋出異常才能被 GlobalExceptionHandler 處理,只要異常最後是從 contoller 層拋出去的就可以被全局異常處理器處理。
  2. 異步方法中的異常不會被全局異常處理。
  3. 拋出的異常如果被代碼內的 try/catch 捕獲了,就不會被 GlobalExceptionHandler 處理了。

總結

本文介紹了在 SpringBoot 中,通過配置全局異常處理器統一處理 Controller 層引發的異常。

優點

減少代碼冗餘,代碼便於維護

缺點

只能處理 controller 層拋出的異常,對例如 Interceptor(攔截器)層的異常、定時任務中的異常、異步方法中的異常,不會進行處理。

以上就是用 @ControllerAdvice + @ExceptionHand 實現 SpringBoot 中捕獲 controller 層全局異常並處理的方法。

附:項目Demo地址:https://github.com/MartinMa94/springboot-globle-exception-demo

有關全局異常的進階使用,請參閱 SpringBoot 全局異常處理進階:使用 @ControllerAdvice 對不同的 Controller 分別捕獲異常並處理


站在前人的肩膀上前行,感謝以下博客及文獻的支持。
@ControllerAdvice + @ExceptionHandler 全局處理 Controller 層異常
Spring Boot 系列(八)@ControllerAdvice 攔截異常並統一處理


原文作者: 一隻因特馬
原文鏈接: https://www.matalking.com/a/2647403060/
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-ND 許可協議。轉載請註明出處!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章