Spring MVC 中的異常處理 (handling exceptions)

在任何應用開發中都需要對異常情況做處理,web應用也是如此。但是在Spring MVC中,所有的Request都是由Servlet處理的,返回的結果都是Response。也就是說,無論請求過程中出現什麼異常,返回的都是一個Response,所有異常信息都要轉換成Response。

當然,Spring提供了多種異常信息到Response信息的轉換方式:

1. 一些特定的Spring異常已經被自動映射特定的http status code
2. 我們可以通過@ResponseStatus註解將一個異常,將其映射到特定的http status code
3. 我們可以通過@ExceptionHandler註解一個方法,由這個方法處理異常

一、將異常映射到http 狀態碼

上面講過,Spring已經將一些特定異常映射成了http 狀態碼:
這裏寫圖片描述

上面列表中的異常一般都是Spring的DispatchServlet在處理請求中經常遇到的異常,比如當DispatchServlet在處理請求過程中找不到controller對應的方法時,便會拋出NoSunchRequestHandlingMethodException,這個異常便會被自動映射成404 錯誤碼。

自動映射的異常是有限的,無法覆蓋應用中遇到的各種異常,幸好Spring提供了@ResponseStatus註解,可以將任何一個異常映射成http狀態碼,下面舉個例子。

比如我們在寫一個註冊業務時,可能會遇到用戶名已存在的問題,此時我們拋出一個用戶已存在異常:

@RequestMapping(value = "/register", method = RequestMethod.POST)
public String processRegistration(User user) {
    if (registerService.isUserExist(user)) {
        throw new UserExistException();
    }
    registerService.register(user);
    return "profile";
}

此時我們希望在我們的業務拋出UserExistException異常時,DispatchServlet能將其映射成400狀態碼,這時我們可以通過@ResponseStatusUserExistException異常進行配置:

@ResponseStatus(value = HttpStatus.BAD_REQUEST,reason="user has exist!")
public class UserExistException extends RuntimeException{

}

@ResponseStatus有兩個屬性,第一個屬性是異常映射的http狀態碼,我們指定其值爲400(bad-request),第二個屬性是原因描述。

我們啓動應用,當我們的業務方法拋出UserExistException時,client端會收到異常指定的錯誤碼和原因描述:
這裏寫圖片描述

二、寫異常處理方法

將異常映射成http 狀態碼可以滿足大部分情形,但是有時候我們不希望異常出現時僅僅返回一個狀態碼,我們還希望能對出現的異常做特殊的處理。還是上面那個例子,當出現UserExistException時我們不返回狀態碼,而是將其重定向到一個錯誤頁面,我們可以這麼處理:

@RequestMapping(value = "/register", method = RequestMethod.POST)
public String processRegistration(User user) {
   try{
       registerService.register(user);
       return "profile";
   }catch(UserExistException e){
       return "error";
   }
}

上面是一種很普通的處理方式,不過上面這種方式還是稍顯複雜,每個業務方法除了關心正常的業務邏輯之外還得處理異常業務邏輯。我們很容易想到,可不可讓一個方法去單獨處理異常?答案當然是可以的,我們可以使用Spring提供的@ExceptionHandler註解,上面例子就可以改造成這樣:

@RequestMapping(value = "/register", method = RequestMethod.POST)
public String processRegistration(User user) {
    registerService.register(user);
    return "profile";
}

//異常處理方法
@ExceptionHandler(UserExistException.class)
public String handleUserExist() {
    return "error";
}

通過@ExceptionHandler註解的使用,業務邏輯只需要處理自己的正常邏輯,異常統統交給異常處理方法進行處理。另外,@ExceptionHandler註解可以捕獲當前controller拋出的任何異常,所以同一個controller中的任何一個業務方法拋出異常,都可以交由一個異常處理方法統一處理。

既然@ExceptionHandler註解可以處理當前controller拋出的任何異常,那麼還有沒有一種方式可以讓它捕獲所有controller拋出的異常呢?答案也是可以的,這就需要用到@ControllerAdvice註解

三、捕獲應用中所有controller異常

@ControllerAdvice註解可以是一個普通的controller變成一個controller advice,即切面controller。在切面controller裏面可以定義三種類型的方法:

1. @ExceptionHandler註解的方法
2. @InitBinder註解的方法
3. @ModelAttribute註解的方法

切面controller裏面的這些方法可以全局應用到所有controller的所有業務方法。有了這個特性,我們就可以利用一個切面controller集中處理應用中的所有異常了。還有,@ControllerAdvice本身已經被@controller註解,所以被@ControllerAdvice註解的類可以被自動掃描到,無需再註解@controller了。下面舉例說明一下。

通過@ControllerAdvice註解定義一個可以處理所有controller異常的切面controller:

@ControllerAdvice
public class AppWideExceptionHandler {
    @ExceptionHandler(UserExistException.class)
    public String handleUserExist() {
        return "error";
    }
}

當應用中的任何一個controller拋出UserExistException異常時,都會被AppWideExceptionHandler中的handleUserExist()方法捕獲處理,這就是我們最期待的。

發佈了98 篇原創文章 · 獲贊 1135 · 訪問量 154萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章