Spring MVC 全局異常處理-RESTAPI接口返回統一JSON格式-自定義異常處理--404異常捕捉


寫之前大概兩周草草的將一些代碼保存在草稿箱,今天有空來看,結果都沒有了【怨念】—重新整理一下了 —–【轉載請標注出處

  • 第一部分:需求
  • 第二部分:實現方式
  • 第三部分:404異常捕捉不能實現分析
  • 第四部分:原因和源碼分析
  • 第五部分:最終總結

需求

  • 本意是想針對對外REST接口的返回格式進行統一,結果404錯誤始終無法被捕捉

實現方式

對於目前的主流方式全部嘗試了一遍,主要有三種,還有其他一些異類采用Filter之類的方式實現,如需可以自取

1. SimpleMappingExceptionResolver
采用xml配置方式,代碼零入侵 。自由性不足,獲取異常信息少(只有異常信息)。
2. HandlerExceptionResolver
自定義類實現該類。可以實現統一異常處理里。我在實現的時候卻沒有生效,而是走了其自定義的DefaultHandlerExceptionResolver,具體原因下面分析
3. @ExceptionHandler注解
這種捕捉方式要求和被捕捉Controller在同一個類中,一般實現方式是把ExceptionHandler放在BaseController中繼承,自由性差。
4.@ControllerAdvice注解
這種需要配合@ExceptionHandler屬於第三種方式變種,我使用的則是這種方式的變種,下面詳解。

原因和源碼分析

首先來分析異常的處理過程

感謝網上某位同學

上面是簡化的請求流轉圖,感謝某位同學的圖片。

下面是程序員趙鑫的原理分析圖,我搬來一用。當然我沒有授權,如果有爭議,我援引互聯網信息共享條例自護(黑人問號臉)
感謝程序員趙鑫同學

照例感謝程序員趙鑫同學,想看再詳細的分析請轉貼這里

上圖是SpringMVC的異常處理器結構,HandlerExceptionResolver是一個接口,留給自定義的時候使用。

異常處理器的處理順序是:異常->HandlerExceptionResolver->自定義異常->默認異常處理器

SpringMVC的異常處理器通過實現Orderd接口,定義Order數值為每個異常處理器排序,圖中可以看到默認異常處理器都繼承於AbstractHandlerExceptionResolver。看圖:

這里寫圖片描述

默認異常處理器都實現了doResolverException()方法。

圖上的每一個處理器其實都代表了可以實現全局自定義處理的一種或者多種方式。

404異常捕捉不能實現分析

在我實現全局處理異常的時候,發現404異常並沒有被捕捉,參考多方資料后發現,SpringleMVC的機制默認是對404異常不進行拋出動作的,直接在Response中設置錯誤代碼,直接返回。具體看圖:

這里寫圖片描述

圖中的屬性 throwExceptionIfNoHandlerFound = false 就是404異常拋出錯誤與否的判斷屬性,下面是判斷源碼,throwExceptionIfNoHandlerFound為true則會拋出異常

這里寫圖片描述

原因和源碼分析

下面對幾種實現方式進行解析

  • 1.SimpleMappingExceptionResolver
    代碼如下:這里不做詳解,缺點是不靈活,獲取信息參數有限
  <!-- 出現異常會跳到這個頁面,總錯誤處理-->
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView">
<value>/error/error</value>
</property>
<property name="defaultStatusCode">
<value>500</value>
</property>
<property name="warnLogCategory">
<value>org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver</value>
</property>
<!-- 如果不設置exceptionMappings,就會全局異常捕獲 -->
<property name="exceptionMappings">
<props>
<prop key="Java.sql.SQLException">/error/error</prop>
<prop key="Java.lang.RuntimeException">/error/error</prop>
<prop key="org.springframework.web.multipart.MaxUploadSizeExceededException">/error/error</prop>
</props>
</property>
</bean>
  • 2.HandlerExceptionResolver
    這種實現方式會受到容器加載順序影響(可能是這個原因),如果沒有加載完全,會按照SpringMVC默認的配置文件中的處理順序進行處理
    這里寫圖片描述
@Component
public class MyExceptionHandler implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
Map<String, Object> model = new HashMap<String, Object>();
model.put("ex", ex);

// 根據不同錯誤轉向不同頁面
if(ex instanceof BusinessException) {
return new ModelAndView("error-business", model);
}else if(ex instanceof ParameterException) {
return new ModelAndView("error-parameter", model);
} else {
return new ModelAndView("error", model);
}
}
}
這種需要在Spring的配置文件applicationContext.xml中增加以下內容:
//有一種說法,如果不起作用,id寫成這樣說不定能解決問題,因為加載的時候是按照這個id去加載的
< bean id="handlerExceptionResolver" class="cn.basttg.core.exception.MyExceptionHandler"/>
  • 3.ControllerAdvice(和ExceptionHandler放在一起了)

@ControllerAdvice
public class ExceptionHandler {

@ResponseBody
@org.springframework.web.bind.annotation.ExceptionHandler(Exception.class//這里可以選擇其他具體的異常)
public ResponseModel handleUnexpectedServerError(Exception ex) {
ex.printStackTrace();

// 處理異常
ResponseModel response = new ResponseModel();

String errorMsg = ex.getMessage();
response.setCode(Integer.valueOf(ResultCode.SYSTEM_EXCEPTION));
if(errorMsg.contains("excpStart")){
errorMsg = errorMsg.substring(errorMsg.indexOf("excpStart") + 9, errorMsg.indexOf("excpEnd"));
}
response.setMessage(errorMsg);

// 返回數據
return response;
}
  • 4.ControllerAdvice(我的實現方式)
@ControllerAdvice
public class GlobalExceptionResolver extends DefaultHandlerExceptionResolver {

@ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String url = request.getServletPath();
if (url.startsWith("/api")) {//api返回異常攔截

if (ex instanceof HttpRequestMethodNotSupportedException) {
setResponseParam(response, 405, "請求方式錯誤!");
return null;
}

if (ex instanceof MissingServletRequestParameterException) {
setResponseParam(response, 400, "錯誤請求!");
return null;
}

if (ex instanceof NoHandlerFoundException) {
//可以進行其他方法處理,LOG或者什么詳細記錄,我這里直接返回JSON
setResponseParam(response, 404, "請求路徑錯誤!");
return null;
}

setResponseParam(response, 500, "服務器內部錯誤!服務暫時不可用!");
return null;
}

//這里調用父類的異常處理方法,實現其他不需要的異常交給SpringMVC處理
return super.doResolveException(request, response, handler, ex);

}

private void setResponseParam(HttpServletResponse response, int code, String msg) throws IOException {
JSONObject j = JSONObject.fromObject(R.error(code, msg));
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(j.toString());
}
}

參考:

公子的專欄焰尾迭程序猿之洞Exception Handling in Spring MVC程序員趙鑫

最終總結

寫了近3小時,一邊回憶一邊寫,好累,下次有好的再說吧,需要支持點這里


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com