寫(xiě)之前大概兩周草草的將一些代碼保存在草稿箱两曼,今天有空來(lái)看钠怯,結(jié)果都沒(méi)有了【怨念】---重新整理一下了 -----【轉(zhuǎn)載請(qǐng)標(biāo)注出處】
- 第一部分:需求
- 第二部分:實(shí)現(xiàn)方式
- 第三部分:404異常捕捉不能實(shí)現(xiàn)分析
- 第四部分:原因和源碼分析
- 第五部分:最終總結(jié)
需求
- **本意是想針對(duì)對(duì)外REST接口的返回格式進(jìn)行統(tǒng)一,結(jié)果
404錯(cuò)誤
始終無(wú)法被捕捉 **
實(shí)現(xiàn)方式
對(duì)于目前的主流方式全部嘗試了一遍凭迹,主要有三種罚屋,還有其他一些異類采用Filter之類的方式實(shí)現(xiàn),如需可以自取
**1. SimpleMappingExceptionResolver **
采用xml配置方式嗅绸,代碼零入侵 脾猛。自由性不足,獲取異常信息少(只有異常信息)朽砰。
2. HandlerExceptionResolver
自定義類實(shí)現(xiàn)該類尖滚。可以實(shí)現(xiàn)統(tǒng)一異常處理里瞧柔。我在實(shí)現(xiàn)的時(shí)候卻沒(méi)有生效漆弄,而是走了其自定義的DefaultHandlerExceptionResolver,具體原因下面分析
3. @ExceptionHandler注解
這種捕捉方式要求和被捕捉Controller在同一個(gè)類中造锅,一般實(shí)現(xiàn)方式是把ExceptionHandler放在BaseController中繼承撼唾,自由性差。
4.@ControllerAdvice注解
這種需要配合@ExceptionHandler屬于第三種方式變種哥蔚,我使用的則是這種方式的變種倒谷,下面詳解蛛蒙。
原因和源碼分析
首先來(lái)分析異常的處理過(guò)程
上面是簡(jiǎn)化的請(qǐng)求流轉(zhuǎn)圖,感謝某位同學(xué)的圖片渤愁。
下面是程序員趙鑫的原理分析圖牵祟,我搬來(lái)一用。當(dāng)然我沒(méi)有授權(quán)抖格,如果有爭(zhēng)議诺苹,我援引互聯(lián)網(wǎng)信息共享?xiàng)l例自護(hù)(黑人問(wèn)號(hào)臉)
照例感謝程序員趙鑫同學(xué),想看再詳細(xì)的分析請(qǐng)轉(zhuǎn)貼這里
上圖是SpringMVC的異常處理器結(jié)構(gòu)雹拄,HandlerExceptionResolver是一個(gè)接口收奔,留給自定義的時(shí)候使用。
異常處理器的處理順序是:異常->HandlerExceptionResolver->自定義異常->默認(rèn)異常處理器
SpringMVC的異常處理器通過(guò)實(shí)現(xiàn)Orderd接口滓玖,定義Order數(shù)值為每個(gè)異常處理器排序坪哄,圖中可以看到默認(rèn)異常處理器都繼承于AbstractHandlerExceptionResolver∈拼郏看圖:
默認(rèn)異常處理器都實(shí)現(xiàn)了doResolverException()方法翩肌。
圖上的每一個(gè)處理器其實(shí)都代表了可以實(shí)現(xiàn)全局自定義處理的一種或者多種方式。
404異常捕捉不能實(shí)現(xiàn)分析
在我實(shí)現(xiàn)全局處理異常的時(shí)候殊霞,發(fā)現(xiàn)404異常并沒(méi)有被捕捉摧阅,參考多方資料后發(fā)現(xiàn),SpringleMVC的機(jī)制默認(rèn)是對(duì)404異常不進(jìn)行拋出動(dòng)作的绷蹲,直接在Response中設(shè)置錯(cuò)誤代碼,直接返回顾孽。具體看圖:
圖中的屬性 throwExceptionIfNoHandlerFound = false 就是404異常拋出錯(cuò)誤與否的判斷屬性,下面是判斷源碼祝钢,throwExceptionIfNoHandlerFound為true則會(huì)拋出異常
原因和源碼分析
下面對(duì)幾種實(shí)現(xiàn)方式進(jìn)行解析
- 1.SimpleMappingExceptionResolver
代碼如下:這里不做詳解,缺點(diǎn)是不靈活若厚,獲取信息參數(shù)有限
<!-- 出現(xiàn)異常會(huì)跳到這個(gè)頁(yè)面拦英,總錯(cuò)誤處理-->
<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>
<!-- 如果不設(shè)置exceptionMappings,就會(huì)全局異常捕獲 -->
<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
這種實(shí)現(xiàn)方式會(huì)受到容器加載順序影響(可能是這個(gè)原因),如果沒(méi)有加載完全测秸,會(huì)按照SpringMVC默認(rèn)的配置文件中的處理順序進(jìn)行處理
@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);
// 根據(jù)不同錯(cuò)誤轉(zhuǎn)向不同頁(yè)面
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中增加以下內(nèi)容:
//有一種說(shuō)法疤估,如果不起作用,id寫(xiě)成這樣說(shuō)不定能解決問(wèn)題霎冯,因?yàn)榧虞d的時(shí)候是按照這個(gè)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);
// 返回?cái)?shù)據(jù)
return response;
}
- 4.ControllerAdvice(我的實(shí)現(xiàn)方式)
@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, "請(qǐng)求方式錯(cuò)誤铃拇!");
return null;
}
if (ex instanceof MissingServletRequestParameterException) {
setResponseParam(response, 400, "錯(cuò)誤請(qǐng)求!");
return null;
}
if (ex instanceof NoHandlerFoundException) {
//可以進(jìn)行其他方法處理沈撞,LOG或者什么詳細(xì)記錄慷荔,我這里直接返回JSON
setResponseParam(response, 404, "請(qǐng)求路徑錯(cuò)誤!");
return null;
}
setResponseParam(response, 500, "服務(wù)器內(nèi)部錯(cuò)誤缠俺!服務(wù)暫時(shí)不可用显晶!");
return null;
}
//這里調(diào)用父類的異常處理方法贷岸,實(shí)現(xiàn)其他不需要的異常交給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、程序員趙鑫唯笙、
最終總結(jié)
聯(lián)系點(diǎn)這里