异常源码解析
DispatcherServlet的doDispatch方法最后调用了如下方法。
最后一个参数就是我们执行过程中出现的异常。
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
processDispatchResult方法
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
this.logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException)exception).getModelAndView();
} else {
Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
mv = this.processHandlerException(request, response, handler, exception);
errorView = mv != null;
}
}
if (mv != null && !mv.wasCleared()) {
this.render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else if (this.logger.isTraceEnabled()) {
this.logger.trace("No view rendering, null ModelAndView returned.");
}
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
}
}
}
如果异常是ModelAndViewDefiningException或其子类异常,则调用其getModelAndView方法进行处理。此方式我们用得较少。
其他异常会调用DispatcherServlet的processHandlerException方法
processHandlerException方法
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
else if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
源码看到这里,终于到了HandlerExceptionResolver异常解析器,而且是一个解析器链。通过HandlerExceptionResolver的resolveException方法处理异常,该方法返回ModelAndView对象。
如图:此处只有两个HandlerExceptionResolver,第一个是默认的DefaultErrorAttributes,是查看源码,非常简单,就是将异常存放到request的attribute。
另一个是HandlerExceptionResolverComposite,查看其resolveException方法如下:
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (this.resolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
我们可以看到HandlerExceptionResolverComposite不是直接处理,而是又维护了一个resolvers 列表,该列表包含的Resolver如下:
代码会依次调用这些resolver,遇到一个resolveException方法不返回null则直接返回对应的mv对象。
内置的 HandlerExceptionResolver
如果在请求映射期间发生异常或从请求处理程序(例如@Controller)抛出异常,则DispatcherServlet把异常委托给HandlerExceptionResolver 异常处理器链来解决异常并返回错误响应。
HandlerExceptionResolver | 描述 |
---|---|
SimpleMappingExceptionResolver | 异常类名称和错误视图名称之间的映射。用于在浏览器应用程序中呈现错误页面。 |
DefaultHandlerExceptionResolver | 解决 Spring MVC 引发的异常并将它们映射到 HTTP 状态代码。 |
ResponseStatusExceptionResolver | 使用@ResponseStatus注释解决异常,并根据注释中的值将它们映射到 HTTP 状态代码。 |
ExceptionHandlerExceptionResolver | 通过调用@ExceptionHandler、@Controller或 @ControllerAdvice类中的方法来解决异常。 |
@ControllerAdvice和@ExceptionHandler处理
@ControllerAdvice和@ExceptionHandler是通过ExceptionHandlerExceptionResolver来处理的,debug后最终会执行到 ExceptionHandlerExceptionResolver 的 doResolveHandlerMethodException 方法。该方法最重要的就是第一句
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
此方法就是获取异常的最终处理方法,也就是我们定义的 @ExceptionHandler 注解的方法。
getExceptionHandlerMethod 方法定义
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = null;
if (handlerMethod != null) {
// Local exception handler methods on the controller class itself.
// To be invoked through the proxy, even in case of an interface-based proxy.
handlerType = handlerMethod.getBeanType();
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
// For advice applicability check below (involving base packages, assignable types
// and annotation presence), use target class instead of interface-based proxy.
if (Proxy.isProxyClass(handlerType)) {
handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
}
}
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);
}
}
}
return null;
}
此方法中只有两个cache我们需要关注,exceptionHandlerCache 和 exceptionHandlerAdviceCache
exceptionHandlerCache
exceptionHandlerCache 在 new ExceptionHandlerMethodResolver(handlerType); 构造方法中我们可以知道,它会扫描当前Controller的所有方法,并且将方法中带有@ExceptionHandler注解的方法放入按异常的类型放入mappedMethods,最终在执行resolver.resolveMethod方法时做匹配处理,得到最终的异常处理方法。
exceptionHandlerAdviceCache
ExceptionHandlerExceptionResolver 在初始化的时候就会扫描 @ControllerAdvice 注解的 bean。初始化的时候也会根据@ControllerAdvice 注解的bean来new ExceptionHandlerMethodResolver放入该缓存。
通过源码我们可以知道,如果在对应的请求所在的 Controller 有 @ExceptionHandler 注解的方法能出来该异常,那么优先使用该方法来处理异常。如果没有则使用 @ControllerAdvice 注解的 bean 中对应的处理方法。如何还没有匹配的处理方法,那么spring MVC将抛出给 servlet 容器处理。
上一篇:Spring MVC全局异常处理
下一篇:CORS跨域介绍