环境:Springboot3.0.5
概述如果在请求映射期间发生异常或从请求处理程序(例如@Controller)抛出异常,DispatcherServlet将委托给HandlerExceptionResolver
(资料图片仅供参考)
下表列出了可用的HandlerExceptionResolver实现。
HandlerExceptionResolver实现类:
HandlerExceptionResolver | 描述 |
SimpleMappingExceptionResolver | 异常类名和错误视图名之间的映射。用于在浏览器应用程序中渲染错误页面。 |
DefaultHandlerExceptionResolver | 解析Spring MVC引发的异常,并将其映射为HTTP状态码。 |
ResponseStatusExceptionResolver | 使用@ResponseStatus注解解析异常,并根据注解中的值将异常映射为HTTP状态码。 |
ExceptionHandlerExceptionResolver | 通过在@Controller或@ControllerAdvice类中调用由@ExceptionHandler注释的方法来解决异常。 |
我们可以声明多个HandlerExceptionResolver
HandlerExceptionResolver的约定规定它可以返回:
指向错误视图的ModelAndView。如果异常是在解析器中处理的,则返回空的ModelAndView。如果异常仍然未解决,则为null,以便后续的解析器尝试,如果异常在最后仍然存在,则允许它向上冒泡到Servlet容器。Controller接口调用原理SpringMVC请求入口通过DispatcherServlet执行大致核心流程如下:
首先通过HandlerMapping确定目标Handler对象(如果接口是Controller那么这里会是HandlerMethod)通过上一步Handler对象,确定执行真正调用的HandlerAdapter这里以Controller接口为例,HandlerAdapter对象为RequestMappingHandlerAdapter
DispatcherServletpublic class DispatcherServlet extends FrameworkServlet { protected void doDispatch(...) throws Exception { HandlerExecutionChain mappedHandler = null; try { Exception dispatchException = null; // 根据请求确定Handler对象(遍历所有的HandlerMapping) mappedHandler = getHandler(processedRequest); // 根据上一步确定的Handler对象,确定HandlerAdapter对象 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 真正执行目标方法的调用 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); }}
RequestMappingHandlerAdapterpublic class RequestMappingHandlerAdapter { protected ModelAndView handleInternal(...) throws Exception { ModelAndView mav; mav = invokeHandlerMethod(request, response, handlerMethod); } protected ModelAndView invokeHandlerMethod(...) throws Exception { // ... ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); // ... 对ServletInvocableHandlerMethod进行配置 invocableMethod.invokeAndHandle(webRequest, mavContainer); return getModelAndView(mavContainer, modelFactory, webRequest); }}
ServletInvocableHandlerMethod执行参数解析目标Controller方法调用及返回值的处理
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { public void invokeAndHandle(...) throws Exception { // 该方法中会进行请求参数的解析及目标方法的调用 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // ... try { // 处理返回值 this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { throw ex; } }}
通过上面的源码分析,在调用过程中如果发生了异常会将异常直接抛出,在DispatcherServlet中会进行异常的处理。
异常解析原理分析接着上面的源码分析,当发生异常后最终会在DispatcherServlet#processDispatchResult方法中进行处理。
public class DispatcherServlet extends FrameworkServlet { /* * 默认情况下有如下2个异常解析器 * 1. DefaultErrorAttributes * 2. ExceptionHandlerExceptionResolver */ private List 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
// 调用父类(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(...) { // 该方法中会先从当前的Controller中查找是否有@ExceptionHandler注解的方法(如果匹配) // 如果没有再从全局的异常处理类句柄中查找 ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); if (exceptionHandlerMethod == null) { return null; } // 执行异常处理方法的调用 exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments); } protected ServletInvocableHandlerMethod getExceptionHandlerMethod(...) { Class> handlerType = null; if (handlerMethod != null) { handlerType = handlerMethod.getBeanType(); // 缓存并设置当前执行Class对应的ExceptionHandlerMethodResolver // ExceptionHandlerMethodResolver构造函数中会解析当前类中的所有方法是否有@ExceptionHandler注解 ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.computeIfAbsent(handlerType, ExceptionHandlerMethodResolver::new); // 解析是否匹配当前发生的异常 Method method = resolver.resolveMethod(exception); if (method != null) { return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method, this.applicationContext); } } // 如果上面的执行的Class中没有找到对应处理器,那么就从全局的异常处理中进行查找匹配 // 这里的exceptionHandlerAdviceCache集合在类初始化执行时已经处理完成 for (Map.Entry 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; }}
通过上面的源码分析你应该知道了关于SpringMVC中异常处理的原理。
当上面的异常处理机制都没法处理,那么将会调用默认的/error接口。
public class ErrorMvcAutoConfiguration { @Bean @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider 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 {}
上面的错误接口/error在容器启动时会自动注册到内嵌的容器中,如:Tomcat。