在任何應用開發中都需要對異常情況做處理,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狀態碼,這時我們可以通過@ResponseStatus
對UserExistException
異常進行配置:
@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()
方法捕獲處理,這就是我們最期待的。