ExceptionHandler是Spring框架提供的一個註解,用於處理應用程式中的異常。當應用程式中發生異常時,ExceptionHandler將優先地攔截異常並處理它,然後將處理結果返回到前端。該註解可用於類級別和方法級別,以捕獲不同級別的異常。
在Spring中使用ExceptionHandler非常簡單,只需在需要捕獲異常的方法上註解@ExceptionHandler,然後定義一個方法,該方法將接收異常並返回異常資訊,並將該異常資訊展示給前端使用者。
說明:針對可能出問題的Controller,新增註解方法@ExceptionHandler,下面是一個基本的ExceptionHandler範例:
@RestController
public class ExceptionController {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An error occurred: " + ex.getMessage());
}
@RequestMapping("/test")
public String test() throws Exception {
throw new Exception("Test exception!");
}
}
在上面的範例中,我們定義了一個叫做ExceptionController的類,該類是一個@RestController註解的控制器,它包括一個可以產生異常的請求處理程式,一個用於捕獲和處理異常的@ExceptionHandler方法。
@RequestMapping註解設定了一個名為「/test」的API,該API將丟擲一個異常,該異常將由我們上面的ExceptionHandler進行處理。當請求「/test」時,Controller方法將引發異常並觸發@ExceptionHandler方法。
在上面的@ExceptionHandler方法中,我們通過ResponseEntity將異常資訊提供給使用者端,HTTP狀態碼設定為500。這使使用者端了解已發生錯誤,並能夠在紀錄檔中記錄異常資訊以便日後偵錯。
總之,使用ExceptionHandler能夠更好的掌控應用的異常資訊,使得應用在發生異常的時候更加可控,並且更加容易進行偵錯。
Controller類下多個@ExceptionHandler上的異常型別不能出現一樣的,否則執行時拋異常。
@ExceptionHandler下方法返回值型別支援多種,常見的ModelAndView,@ResponseBody註解標註,ResponseEntity等型別都OK.
程式碼片段位於:org.springframework.web.servlet.DispatcherServlet#doDispatch
執行@RequestMapping方法丟擲異常後,Spring框架 try-catch的方法捕獲異常, 正常邏輯發不發生異常都會走processDispatchResult流程 ,區別在於異常的引數是否為null .
HandlerExecutionChain mappedHandler = null;
Exception dispatchException = null;
ModelAndView mv = null;
try{
//根據請求查詢handlerMapping找到controller
mappedHandler=getHandler(request);
//找到處理器介面卡HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
if(!mappedHandler.applyPreHandle(request,response)){
//攔截器preHandle
return ;
}
//呼叫處理器介面卡執行@RequestMapping方法
mv=ha.handle(request,response);
//攔截器postHandle
mappedHandler.applyPostHandle(request,response,mv);
}catch(Exception ex){
dispatchException=ex;
}
//將異常資訊傳入了
processDispatchResult(request,response,mappedHandler,mv,dispatchException)
程式碼片段位於:org.springframework.web.servlet.DispatcherServlet#processDispatchResult
如果 @RequestMapping 方法丟擲異常,攔截器的postHandle方法不執行,進入processDispatchResult,判斷入參dispatchException,不為null , 代表發生異常,呼叫processHandlerException處理。
程式碼片段位於:org.springframework.web.servlet.DispatcherServlet#processHandlerException
this當前物件指dispatchServlet,handlerExceptionResolvers可以看到三個HandlerExceptionResolver,這三個是Spring框架幫我們註冊的,遍歷有序集合handlerExceptionResolvers,呼叫介面的resolveException方法。
註冊的第一個HandlerExceptionResolver.ExceptionHandlerExceptionResolver, 繼承關係如下面所示。
程式碼片段位於:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#resolveException
這裡AbstractHandlerExceptionResolver的shouldApplyTo都返回true, logException用來記錄紀錄檔、prepareResponse方法,用來設定response的Cache-Control。
例外處理方法就位於doResolveException
注意:AbstractHandlerExceptionResolver和AbstractHandlerMethodExceptionResolver名字看起來非常相似,但是作用不同,一個是面向整個類的,一個是面向方法級別的。
程式碼片段位於:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#shouldApplyTo
介面方法實現AbstractHandlerExceptionResolver的resolveException,先判斷shouldApplyTo,AbstractHandlerExceptionResolver 和子類AbstractHandlerMethodExceptionResolver都實現了shouldApplyTo方法,子類的shouldApplyTo都呼叫父類別AbstractHandlerExceptionResolver的shouldApplyTo.
程式碼片段位於:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#shouldApplyTo
Spring初始化的時候並沒有額外設定 , 所以mappedHandlers和mappedHandlerClasses都為null, 可以在這塊擴充套件進行篩選 ,AbstractHandlerExceptionResolver提供了setMappedHandlerClasses 、setMappedHandlers用於擴充套件。
程式碼片段位於:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#doResolveException
Spring請求方法執行一樣的處理方式,設定argumentResolvers、returnValueHandlers,之後進行呼叫例外處理方法。
@ExceptionHandler的方法入參支援:Exception ;SessionAttribute 、 RequestAttribute註解、 HttpServletRequest 、HttpServletResponse、HttpSession。
@ExceptionHandler方法返回值常見的可以是: ModelAndView 、@ResponseBody註解、ResponseEntity。
getExceptionHandlerMethod說明: 獲取對應的@ExceptionHandler方法,封裝成ServletInvocableHandlerMethod返回。
exceptionHandlerCache是針對Controller層面的@ExceptionHandler的處理方式,而exceptionHandlerAdviceCache是針對@ControllerAdvice的處理方式. 這兩個屬性都位於ExceptionHandlerExceptionResolver中。
ExceptionHandlerMethodResolver,快取A之前沒儲存過Controller的class ,所以新建一個ExceptionHandlerMethodResolver 加入快取中,ExceptionHandlerMethodResolver 的初始化工作一定做了某些工作。
根據異常物件讓 ExceptionHandlerMethodResolver 解析得到 method , 匹配到例外處理方法就直接封裝成物件 ServletInvocableHandlerMethod ; 就不會再去走@ControllerAdvice裡的例外處理器了,這裡說明了。
resolveMethodByExceptionType根據當前丟擲異常尋找 匹配的方法,並且做了快取,以後遇到同樣的異常可以直接走快取取出
resolveMethodByExceptionType方法,嘗試從快取A:exceptionLookupCache中根據異常class型別獲取Method ,初始時候肯定快取為空 ,就去遍歷ExceptionHandlerMethodResolver的mappedMethods(上面提及了key為異常型別,value為method,exceptionType為當前@RequestMapping方法丟擲的異常,判斷當前異常型別是不是@ExceptionHandler中value宣告的子類或本身,滿足條件就代表匹配上了;
可能存在多個匹配的方法,使用ExceptionDepthComparator排序,排序規則是按照繼承順序來(繼承關係越靠近數值越小,當前類最小為0,頂級父類別Throwable為int最大值),排序之後選取繼承關係最靠近的那個,並且ExceptionHandlerMethodResolver的exceptionLookupCache中,key為當前丟擲的異常,value為解析出來的匹配method.
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelMap mmp=new ModelMap();
mmp.addAttribute("ex",ex.getMessage());
return new ModelAndView("error",mmp);
}
}
使用方式: 只需要將該Bean加入到Spring容器,可以通過Xml設定,也可以通過註解方式加入容器;
方法返回值不為null才有意義,如果方法返回值為null,可能異常就沒有被捕獲.
缺點分析:比如這種方式全域性例外處理返回JSP、velocity等檢視比較方便,返回json或者xml等格式的響應就需要自己實現了.如下是我實現的發生全域性異常返回JSON的簡單例子.
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("發生全域性異常!");
ModelMap mmp=new ModelMap();
mmp.addAttribute("ex",ex.getMessage());
response.addHeader("Content-Type","application/json;charset=UTF-8");
try {
new ObjectMapper().writeValue(response.getWriter(),ex.getMessage());
response.getWriter().flush();
} catch (IOException e) {
e.printStackTrace();
}
return new ModelAndView();
}
}
用法說明:這種情況下 @ExceptionHandler與第一種方式用法相同,返回值支援ModelAndView,@ResponseBody等多種形式。
@ControllerAdvice
public class GlobalController {
@ExceptionHandler(RuntimeException.class)
public ModelAndView fix1(Exception e){
System.out.println("全域性的例外處理器");
ModelMap mmp=new ModelMap();
mmp.addAttribute("ex",e);
return new ModelAndView("error",mmp);
}
}
現在問題的關鍵就只剩下了exceptionHandlerAdviceCache是什麼時候掃描@ControllerAdvice的,下面的邏輯和@ExceptionHandler的邏輯一樣了,exceptionHandlerAdviceCache初始化邏輯:
程式碼片段位於:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#afterPropertiesSet,afterPropertiesSet是Spring bean建立過程中一個重要環節。
程式碼片段位於:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache
ControllerAdviceBean.findAnnotatedBeans方法查詢了SpringMvc父子容器中標註 @ControllerAdvice 的bean, new ExceptionHandlerMethodResolver初始化時候解析了當前的@ControllerAdvice的bean的@ExceptionHandler,加入到ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中,key為ControllerAdviceBean,value為ExceptionHandlerMethodResolver . 到這裡exceptionHandlerAdviceCache就初始化完畢。
程式碼片段位於:org.springframework.web.method.ControllerAdviceBean#findAnnotatedBeans
遍歷了SpringMVC父子容器中所有的bean,標註ControllerAdvice註解的bean加入集合返回。
@Controller+@ExceptionHandler、HandlerExceptionResolver介面形式、@ControllerAdvice+@ExceptionHandler優缺點說明:
三種方式並存的情況 優先順序越高的越先選擇,而且被一個捕獲處理了就不去執行其他的。
@Controller+@ExceptionHandler、@ControllerAdvice+@ExceptionHandler可以使用Spring支援的@ResponseBody、ResponseEntity。
HandlerExceptionResolver方法宣告返回值型別只能是 ModelAndView,如果需要返回JSON、xml等需要自己實現.。
@Controller+@ExceptionHandler的快取資訊在ExceptionHandlerExceptionResolver的exceptionHandlerCache,@ControllerAdvice+@ExceptionHandler的快取資訊在ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中,
HandlerExceptionResolver介面是不做快取的,在異常報錯的情況下才會走自己的HandlerExceptionResolver實現類,多少有點效能損耗.
本文來自部落格園,作者:洛神灬殤,轉載請註明原文連結:https://www.cnblogs.com/liboware/p/17300606.html,任何足夠先進的科技,都與魔法無異。