ExceptionHandler的作用
ExceptionHandler是Spring框架提供的一個注解,用于處理應(yīng)用程序中的異常。當(dāng)應(yīng)用程序中發(fā)生異常時,ExceptionHandler將優(yōu)先地攔截異常并處理它,然后將處理結(jié)果返回到前端。該注解可用于類級別和方法級別,以捕獲不同級別的異常。
在Spring中使用ExceptionHandler非常簡單,只需在需要捕獲異常的方法上注解@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注解的控制器,它包括一個可以產(chǎn)生異常的請求處理程序,一個用于捕獲和處理異常的@ExceptionHandler方法。
@RequestMapping注解配置了一個名為“/test”的API,該API將拋出一個異常,該異常將由我們上面的ExceptionHandler進行處理。當(dāng)請求“/test”時,Controller方法將引發(fā)異常并觸發(fā)@ExceptionHandler方法。
在上面的@ExceptionHandler方法中,我們通過ResponseEntity將異常信息提供給客戶端,HTTP狀態(tài)碼設(shè)置為500。這使客戶端了解已發(fā)生錯誤,并能夠在日志中記錄異常信息以便日后調(diào)試。
總之,使用ExceptionHandler能夠更好的掌控應(yīng)用的異常信息,使得應(yīng)用在發(fā)生異常的時候更加可控,并且更加容易進行調(diào)試。
ExceptionHandler的注意事項
-
Controller類下多個@ExceptionHandler上的異常類型不能出現(xiàn)一樣的,否則運行時拋異常。
-
@ExceptionHandler下方法返回值類型支持多種,常見的ModelAndView,@ResponseBody注解標(biāo)注,ResponseEntity等類型都OK.
源碼分析介紹
原理說明-doDispatch
代碼片段位于:org.springframework.web.servlet.DispatcherServlet#doDispatch
執(zhí)行@RequestMapping方法拋出異常后,Spring框架 try-catch的方法捕獲異常, 正常邏輯發(fā)不發(fā)生異常都會走processDispatchResult流程 ,區(qū)別在于異常的參數(shù)是否為null .
HandlerExecutionChain mappedHandler = null;
Exception dispatchException = null;
ModelAndView mv = null;
try{
//根據(jù)請求查找handlerMapping找到controller
mappedHandler=getHandler(request);
//找到處理器適配器HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
if(!mappedHandler.applyPreHandle(request,response)){
//攔截器preHandle
return ;
}
//調(diào)用處理器適配器執(zhí)行@RequestMapping方法
mv=ha.handle(request,response);
//攔截器postHandle
mappedHandler.applyPostHandle(request,response,mv);
}catch(Exception ex){
dispatchException=ex;
}
//將異常信息傳入了
processDispatchResult(request,response,mappedHandler,mv,dispatchException)
原理說明-processDispatchResult
代碼片段位于:org.springframework.web.servlet.DispatcherServlet#processDispatchResult
如果 @RequestMapping 方法拋出異常,攔截器的postHandle方法不執(zhí)行,進入processDispatchResult,判斷入?yún)ispatchException,不為null , 代表發(fā)生異常,調(diào)用processHandlerException處理。
原理說明-processHandlerException
代碼片段位于:org.springframework.web.servlet.DispatcherServlet#processHandlerException
this當(dāng)前對象指dispatchServlet,handlerExceptionResolvers可以看到三個HandlerExceptionResolver,這三個是Spring框架幫我們注冊的,遍歷有序集合handlerExceptionResolvers,調(diào)用接口的resolveException方法。
注冊的第一個HandlerExceptionResolver.ExceptionHandlerExceptionResolver, 繼承關(guān)系如下面所示。
原理說明-AbstractHandlerExceptionResolver
代碼片段位于:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#resolveException
這里AbstractHandlerExceptionResolver的shouldApplyTo都返回true, logException用來記錄日志、prepareResponse方法,用來設(shè)置response的Cache-Control。
異常處理方法就位于doResolveException
注意:AbstractHandlerExceptionResolver和AbstractHandlerMethodExceptionResolver名字看起來非常相似,但是作用不同,一個是面向整個類的,一個是面向方法級別的。
原理說明-AbstractHandlerMethodExceptionResolver
代碼片段位于:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#shouldApplyTo
接口方法實現(xiàn)AbstractHandlerExceptionResolver的resolveException,先判斷shouldApplyTo,AbstractHandlerExceptionResolver 和子類AbstractHandlerMethodExceptionResolver都實現(xiàn)了shouldApplyTo方法,子類的shouldApplyTo都調(diào)用父類AbstractHandlerExceptionResolver的shouldApplyTo.
父類AbstractHandlerExceptionResolver的shouldApplyTo方法.
代碼片段位于:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#shouldApplyTo
Spring初始化的時候并沒有額外配置 , 所以mappedHandlers和mappedHandlerClasses都為null, 可以在這塊擴展進行篩選 ,AbstractHandlerExceptionResolver提供了setMappedHandlerClasses 、setMappedHandlers用于擴展。
doResolveException
代碼片段位于:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#doResolveException
Spring請求方法執(zhí)行一樣的處理方式,設(shè)置argumentResolvers、returnValueHandlers,之后進行調(diào)用異常處理方法。
獲取@ExceptionHandler
@ExceptionHandler的方法入?yún)⒅С郑篍xception ;SessionAttribute 、 RequestAttribute注解、 HttpServletRequest 、HttpServletResponse、HttpSession。
@ExceptionHandler方法返回值常見的可以是: ModelAndView 、@ResponseBody注解、ResponseEntity。
getExceptionHandlerMethod方法
getExceptionHandlerMethod說明: 獲取對應(yīng)的@ExceptionHandler方法,封裝成ServletInvocableHandlerMethod返回。
exceptionHandlerCache是針對Controller層面的@ExceptionHandler的處理方式,而exceptionHandlerAdviceCache是針對@ControllerAdvice的處理方式. 這兩個屬性都位于ExceptionHandlerExceptionResolver中。
ExceptionHandlerMethodResolver,緩存A之前沒存儲過Controller的class ,所以新建一個ExceptionHandlerMethodResolver 加入緩存中,ExceptionHandlerMethodResolver 的初始化工作一定做了某些工作。
resolveMethod方法
根據(jù)異常對象讓 ExceptionHandlerMethodResolver 解析得到 method , 匹配到異常處理方法就直接封裝成對象 ServletInvocableHandlerMethod ; 就不會再去走@ControllerAdvice里的異常處理器了,這里說明了。
resolveMethodByExceptionType根據(jù)當(dāng)前拋出異常尋找 匹配的方法,并且做了緩存,以后遇到同樣的異??梢灾苯幼呔彺嫒〕?br>
resolveMethodByExceptionType方法,嘗試從緩存A:exceptionLookupCache中根據(jù)異常class類型獲取Method ,初始時候肯定緩存為空 ,就去遍歷ExceptionHandlerMethodResolver的mappedMethods(上面提及了key為異常類型,value為method,exceptionType為當(dāng)前@RequestMapping方法拋出的異常,判斷當(dāng)前異常類型是不是@ExceptionHandler中value聲明的子類或本身,滿足條件就代表匹配上了;
可能存在多個匹配的方法,使用ExceptionDepthComparator排序,排序規(guī)則是按照繼承順序來(繼承關(guān)系越靠近數(shù)值越小,當(dāng)前類最小為0,頂級父類Throwable為int最大值),排序之后選取繼承關(guān)系最靠近的那個,并且ExceptionHandlerMethodResolver的exceptionLookupCache中,key為當(dāng)前拋出的異常,value為解析出來的匹配method.
全局級別異常處理器實現(xiàn)HandlerExceptionResolver接口
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等格式的響應(yīng)就需要自己實現(xiàn)了.如下是我實現(xiàn)的發(fā)生全局異常返回JSON的簡單例子.
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("發(fā)生全局異常!");
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();
}
}
全局級別異常處理器@ControllerAdvice+@ExceptionHandler使用方法
用法說明:這種情況下 @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);
}
}
- 方式一:提到ExceptionHandlerExceptionResolver不僅維護@Controller級別的@ExceptionHandler,同時還維護的@ControllerAdvice級別的@ExceptionHandler代碼片段位于:
isApplicableToBeanType方法是用來做條件判斷的,@ControllerAdvice注解有很多屬性用來設(shè)置條件,
basePackageClasses、assignableTypes、annotations等,比如我限定了annotations為注解X, 那標(biāo)注了@X 的ControllerA就可以走這個異常處理器,ControllerB就不能走這個異常處理器。
現(xiàn)在問題的關(guān)鍵就只剩下了exceptionHandlerAdviceCache是什么時候掃描@ControllerAdvice的,下面的邏輯和@ExceptionHandler的邏輯一樣了,exceptionHandlerAdviceCache初始化邏輯:
代碼片段位于:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#afterPropertiesSet,afterPropertiesSet是Spring bean創(chuàng)建過程中一個重要環(huán)節(jié)。
代碼片段位于:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache
ControllerAdviceBean.findAnnotatedBeans方法查找了SpringMvc父子容器中標(biāo)注 @ControllerAdvice 的bean, new ExceptionHandlerMethodResolver初始化時候解析了當(dāng)前的@ControllerAdvice的bean的@ExceptionHandler,加入到ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中,key為ControllerAdviceBean,value為ExceptionHandlerMethodResolver . 到這里exceptionHandlerAdviceCache就初始化完畢。
Spring父子容器中所有@ControllerAdivce的bean的方法
代碼片段位于:org.springframework.web.method.ControllerAdviceBean#findAnnotatedBeans
遍歷了SpringMVC父子容器中所有的bean,標(biāo)注ControllerAdvice注解的bean加入集合返回。
比較說明
@Controller+@ExceptionHandler、HandlerExceptionResolver接口形式、@ControllerAdvice+@ExceptionHandler優(yōu)缺點說明:
調(diào)用優(yōu)先級
- @Controller+@ExceptionHandler優(yōu)先級最高
- @ControllerAdvice+@ExceptionHandler 略低
- HandlerExceptionResolver最低。
三種方式并存的情況 優(yōu)先級越高的越先選擇,而且被一個捕獲處理了就不去執(zhí)行其他的。
三種方式都支持多種返回類型
-
@Controller+@ExceptionHandler、@ControllerAdvice+@ExceptionHandler可以使用Spring支持的@ResponseBody、ResponseEntity。
-
HandlerExceptionResolver方法聲明返回值類型只能是 ModelAndView,如果需要返回JSON、xml等需要自己實現(xiàn).。
緩存利用
-
@Controller+@ExceptionHandler的緩存信息在ExceptionHandlerExceptionResolver的exceptionHandlerCache,@ControllerAdvice+@ExceptionHandler的緩存信息在ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中,文章來源:http://www.zghlxwxcb.cn/news/detail-408738.html
-
HandlerExceptionResolver接口是不做緩存的,在異常報錯的情況下才會走自己的HandlerExceptionResolver實現(xiàn)類,多少有點性能損耗.文章來源地址http://www.zghlxwxcb.cn/news/detail-408738.html
到了這里,關(guān)于【Spring專題】「技術(shù)原理」從源碼角度去深入分析關(guān)于Spring的異常處理ExceptionHandler的實現(xiàn)原理的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!