詳解SpringBoot接口異常處理機(jī)制及源碼分析
環(huán)境:Springboot3.0.5
概述
如果在請(qǐng)求映射期間發(fā)生異?;驈恼?qǐng)求處理程序(例如@Controller)拋出異常,DispatcherServlet將委托給HandlerExceptionResolver。
下表列出了可用的HandlerExceptionResolver實(shí)現(xiàn)。
HandlerExceptionResolver 實(shí)現(xiàn)類:
HandlerExceptionResolver | 描述 |
SimpleMappingExceptionResolver | 異常類名和錯(cuò)誤視圖名之間的映射。用于在瀏覽器應(yīng)用程序中渲染錯(cuò)誤頁面。 |
DefaultHandlerExceptionResolver | 解析Spring MVC引發(fā)的異常,并將其映射為HTTP狀態(tài)碼。 |
ResponseStatusExceptionResolver | 使用@ResponseStatus注解解析異常,并根據(jù)注解中的值將異常映射為HTTP狀態(tài)碼。 |
ExceptionHandlerExceptionResolver | 通過在@Controller或@ControllerAdvice類中調(diào)用由@ExceptionHandler注釋的方法來解決異常。 |
我們可以聲明多個(gè)HandlerExceptionResolver
HandlerExceptionResolver的約定規(guī)定它可以返回:
- 指向錯(cuò)誤視圖的ModelAndView。
- 如果異常是在解析器中處理的,則返回空的ModelAndView。
- 如果異常仍然未解決,則為null,以便后續(xù)的解析器嘗試,如果異常在最后仍然存在,則允許它向上冒泡到Servlet容器。
Controller接口調(diào)用原理
SpringMVC請(qǐng)求入口通過DispatcherServlet執(zhí)行大致核心流程如下:
- 首先通過HandlerMapping確定目標(biāo)Handler對(duì)象(如果接口是Controller那么這里會(huì)是 HandlerMethod)
- 通過上一步Handler對(duì)象,確定執(zhí)行真正調(diào)用的HandlerAdapter
這里以Controller接口為例,HandlerAdapter對(duì)象為RequestMappingHandlerAdapter。
DispatcherServlet
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(...) throws Exception {
HandlerExecutionChain mappedHandler = null;
try {
Exception dispatchException = null;
// 根據(jù)請(qǐng)求確定Handler對(duì)象(遍歷所有的HandlerMapping)
mappedHandler = getHandler(processedRequest);
// 根據(jù)上一步確定的Handler對(duì)象,確定HandlerAdapter對(duì)象
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 真正執(zhí)行目標(biāo)方法的調(diào)用
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
} catch (Exception ex) {
dispatchException = ex;
} catch (Throwable err) {
dispatchException = new ServletException("Handler dispatch failed: " + err, err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
}
RequestMappingHandlerAdapter
public class RequestMappingHandlerAdapter {
protected ModelAndView handleInternal(...) throws Exception {
ModelAndView mav;
mav = invokeHandlerMethod(request, response, handlerMethod);
}
protected ModelAndView invokeHandlerMethod(...) throws Exception {
// ...
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// ... 對(duì)ServletInvocableHandlerMethod進(jìn)行配置
invocableMethod.invokeAndHandle(webRequest, mavContainer);
return getModelAndView(mavContainer, modelFactory, webRequest);
}
}
ServletInvocableHandlerMethod執(zhí)行參數(shù)解析目標(biāo)Controller方法調(diào)用及返回值的處理。
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
public void invokeAndHandle(...) throws Exception {
// 該方法中會(huì)進(jìn)行請(qǐng)求參數(shù)的解析及目標(biāo)方法的調(diào)用
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// ...
try {
// 處理返回值
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
} catch (Exception ex) {
throw ex;
}
}
}
通過上面的源碼分析,在調(diào)用過程中如果發(fā)生了異常會(huì)將異常直接拋出,在DispatcherServlet中會(huì)進(jìn)行異常的處理。
異常解析原理分析
接著上面的源碼分析,當(dāng)發(fā)生異常后最終會(huì)在DispatcherServlet#processDispatchResult方法中進(jìn)行處理。
public class DispatcherServlet extends FrameworkServlet {
/*
* 默認(rèn)情況下有如下2個(gè)異常解析器
* 1. DefaultErrorAttributes
* 2. ExceptionHandlerExceptionResolver
*/
private List<HandlerExceptionResolver> handlerExceptionResolvers;
private void processDispatchResult(...) {
if (exception != null) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// 處理異常
mv = processHandlerException(request, response, handler, exception);
}
}
protected ModelAndView processHandlerException(...) throws Exception {
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
// 遍歷所有的異常解析器
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
// 解析異常,核心的解析器是ExceptionHandlerExceptionResolver
exMv = resolver.resolveException(request, response, handler, ex);
}
}
// ...
}
}
ExceptionHandlerExceptionResolver類繼承自AbstractHandlerMethodExceptionResolver該類又繼承自AbstractHandlerExceptionResolver。
// 調(diào)用父類(AbstractHandlerExceptionResolver)方法
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
public ModelAndView resolveException(...) {
// doResolveException該方法在子類AbstractHandlerMethodExceptionResolver中重寫
ModelAndView result = doResolveException(request, response, handler, ex);
}
}
AbstractHandlerMethodExceptionResolver
public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver {
protected final ModelAndView doResolveException(...) {
HandlerMethod handlerMethod = (handler instanceof HandlerMethod hm ? hm : null);
return doResolveHandlerMethodException(request, response, handlerMethod, ex);
}
}
ExceptionHandlerExceptionResolver
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean {
protected ModelAndView doResolveHandlerMethodException(...) {
// 該方法中會(huì)先從當(dāng)前的Controller中查找是否有@ExceptionHandler注解的方法(如果匹配)
// 如果沒有再?gòu)娜值漠惓L幚眍惥浔胁檎? ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
// 執(zhí)行異常處理方法的調(diào)用
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
}
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(...) {
Class<?> handlerType = null;
if (handlerMethod != null) {
handlerType = handlerMethod.getBeanType();
// 緩存并設(shè)置當(dāng)前執(zhí)行Class對(duì)應(yīng)的ExceptionHandlerMethodResolver
// ExceptionHandlerMethodResolver構(gòu)造函數(shù)中會(huì)解析當(dāng)前類中的所有方法是否有@ExceptionHandler注解
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.computeIfAbsent(handlerType, ExceptionHandlerMethodResolver::new);
// 解析是否匹配當(dāng)前發(fā)生的異常
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method, this.applicationContext);
}
}
// 如果上面的執(zhí)行的Class中沒有找到對(duì)應(yīng)處理器,那么就從全局的異常處理中進(jìn)行查找匹配
// 這里的exceptionHandlerAdviceCache集合在類初始化執(zhí)行時(shí)已經(jīng)處理完成
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
ControllerAdviceBean advice = entry.getKey();
if (advice.isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(advice.resolveBean(), method, this.applicationContext);
}
}
}
return null;
}
}
通過上面的源碼分析你應(yīng)該知道了關(guān)于SpringMVC中異常處理的原理。
當(dāng)上面的異常處理機(jī)制都沒法處理,那么將會(huì)調(diào)用默認(rèn)的/error接口。
public class ErrorMvcAutoConfiguration {
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(), errorViewResolvers.orderedStream().toList());
}
}
BasicErrorController
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
}
上面的錯(cuò)誤接口/error在容器啟動(dòng)時(shí)會(huì)自動(dòng)注冊(cè)到內(nèi)嵌的容器中,如:Tomcat。