使用Spring MVC開發的博客網站時,遇到了如何處理業務層拋出的異常的問題,查閱到了spring官方博客-spring MVC中異常的處理,以下將會以登錄模塊為示例。
愚蠢的處理方式
處理異常遵循“早拋出,晚捕獲"的原則,在controller
中統一處理異常,調用業務邏輯service
時使用try-catch
包圍。
然而這樣需要每個controller
方法中會編寫模版代碼,自然Spring MVC的設計者也會想到這個問題!於是去查閱資料。
優雅的解決方案
Spring MVC提供的3類處理方式,實現在控制層controller
的外圍處理異常,大概示意圖如下:
- 基於異常類(自定義),即針對某類異常;
- 基於控制器(
controller
),即針對某個控制器; - 全局異常處理;
Spring MVC異常處理
LoginController
控制器中處理登錄的方法login
,代碼如下:
/**
* 登錄
* @param username
* @param password
* @param session
* @return
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public LuoblogResult<Author> login(String username, String password, HttpSession session) {
// 登錄的業務邏輯
// 當用戶名或密碼錯誤將拋出異常
Author author = authorService.login(username, password);
return new LuoblogResult<Author>(true, author);
}
為了和Spring異常處理產生對比,這里不使用任何異常處理,瀏覽器中輸入不存在的用戶,執行結果如下:
注:作者名不存在,業務層會拋出BusinessException異常;
1.基於自定異常類處理
是時候讓我們嘗嘗Spring異常處理這個香餑餑,只需要使用@ResponseState注解對自定義異常類進行標注,Spring在處理異常的時候會利用反射對該注解標記的異常特殊處理(個人推測):
/**
* 業務異常
* Created by luokaiqiongmou on 2016/12/6.
*/
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "業務異常,偽裝為錯誤請求")
public class BusinessException extends RuntimeException{
// 一些內容
}
現在再來登錄一次,輸入不存在的用戶名,執行結果如下:
巧妙的將原本的500錯誤,修改為400錯誤,那這有什么用了,這樣你就可以控制對應異常發生時轉化為其他的狀態碼,便於前端處理。
2.基於控制器,處理異常
現在我們可以針對某個控制器處理異常,只需要在控制器中增加一個異常處理方法,並使用@ExceptionHandler
標注:
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "業務異常,針對控制器處理")
@ExceptionHandler(BusinessException.class)
public void conflict() {
// 不做任何事或者可以做任何事
}
同樣登錄一次,結果如下:
注:@ExceptionHandler
標注的方法,方法簽名靈活、多變。被@ResponseStatus注解的方法將會修改相應狀態碼,而使用@ResponseBody
可以返回json格式的數據,再供前端處理。參考:示例
3. 全局的異常處理
針對某個控制器處理異常的方式會造成代碼入侵
方式一:使用注解
現在創建一個專門處理異常的類,並添加@ControllerAdvice
注解,如下:
/**
* 異常處理
* Created by luokaiqiongmou on 2016/12/17.
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(BusinessException.class)
@ResponseBody
public LuoblogResult<Object> handle(BusinessException e) {
logger.warn("GloabalExceptionHandler handing a Exception: " + e.getMessage());
// 業務失敗返回
return new LuoblogResult<Object>(false, e.getDescription());
}
}
再次登錄,效果如下:
方式二:實現HandlerExceptionResolver,並注入到spring容器中
創建一個類實現HandlerExceptionResolver,並在配置文件中注入bean,參考:自定義異常處理器,以下方式未做測試
Spring已經為我們預定義了一個處理異常的解析類SimpleMappingExceptionResolve,添加如下配置文件即可:
<bean id="simpleMappingExceptionResolver" class=
"org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<map>
<!-- key:異常類別(非全稱), 視圖名稱 -->
<entry key="DatabaseException" value="databaseError"/>
<entry key="InvalidCreditCardException" value="creditCardError"/>
</map>
</property>
<!-- 默認的錯誤處理頁面,異常的名稱 -->
<property name="defaultErrorView" value="error"/>
<property name="exceptionAttribute" value="ex"/>
<!-- Name of logger to use to log exceptions. Unset by default,
so logging is disabled unless you set a value. -->
<property name="warnLogCategory" value="example.MvcLogger"/>
</bean>
總結
三種異常處理方式,主要涉及3個注解和1個接口,@ExceptionHandler
標注的方法被定義為處理指定類型異常;@ResponseStatus
標注的方法執行,會修改響應頭中的狀態碼;Spring會把@ControllerAdvice
的類內部使用@ExceptionHandler方法應用到所有的 @RequestMapping注解的方法上。
- 基於異常的處理方式,第一種無法實現json數據返回
- 基於控制器的方式
- 全局的方式!