赞
踩
https://www.bilibili.com/video/BV1sS411c7Mo
最近在做系统升级的时候,引发了一个BUG,原本系统是有一个异常处理器A,引入了某个底包中也带了一个异常处理器B,最终走了底包的异常处理器B。 A对于异常的时候会返回HTTP状态码为500,B对于异常处理器返回的HTTP状态码为200,前端基于HTTP状态码进行提示的,就出了问题
本篇文章我们就来讨论一下在JavaWeb中的全局异常处理器是何时何地如何执行的。
在进行学习之前需要先知道:HTTP执行流程,SpringMVC执行流程
全局异常处理器的父接口是 HandlerExceptionResolver,简单来说就是实现或间接实现它的类就叫全局异常处理器。
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
HandlerExceptionResolver 的继承关系图
SpringBoot项目最大的特点就是注解,在SpringBoot项目中全局异常拦截的注解是@ControllerAdvice (@RestControllerAdvice = @ControllerAdvice + @ResponseBody)
使用 @ControllerAdvice的类最终会生成 ExceptionHandlerExceptionResolver
重写 doResolveHandlerMethodException 方法,然后注册当前的bean
public class ExceptionHandler extends AbstractHandlerMethodExceptionResolver {
private final static Logger logger = LoggerFactory.getLogger(ExceptionHandler.class);
@Override
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception ex) {
return new ModelAndView();
}
}
org.springframework.web.servlet.DispatcherServlet#doDispatch 这个方法就是SpringMVC的执行流程的核心代码了,下面是简化代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { // ... mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // .... } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed", err); } // 异常处理的入口 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { // .... } catch (Throwable err) { // .... } finally { // ... } }
org.springframework.web.servlet.DispatcherServlet#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) { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); // 异常处理 mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } // ... }
org.springframework.web.servlet.DispatcherServlet#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) { // 遍历循环所有的拦截器来尝试处理这个异常(拦截器已经按照 order 排好序了) for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) { exMv = resolver.resolveException(request, response, handler, ex); // 只有返回了 ModelAndView 才结束,不然一直往下走 if (exMv != null) { break; } } } // ... // 如果没有全局异常处理器 可以处理这个异常 就继续抛出去 throw ex; }
上面看到是从 handlerExceptionResolvers 从获取所有的异常处理器,它是一个list
@Nullable
private List<HandlerExceptionResolver> handlerExceptionResolvers;
在DispatcherServlet里面有一个onRefresh方法,它是重写的父类FrameworkServlet的,在初始化ServletBean的时候会被调用一次,它里面会做很多初始化的操作,其中一个就是获取容器里面的全局异常拦截器
一层层看上去其实是 Servlet接口的 init方法触发的
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
// ...
initHandlerExceptionResolvers(context);
// ...
}
找到bean容器里面的所有异常拦截器,把它存在 handlerExceptionResolvers 里面,并排序
private void initHandlerExceptionResolvers(ApplicationContext context) { this.handlerExceptionResolvers = null; if (this.detectAllHandlerExceptionResolvers) { // 从bean容器里面找到所有的 HandlerExceptionResolver Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values()); // 排序 AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); } } // ... }
在springframework 中有这样一个类 ExceptionHandlerExceptionResolver
package org.springframework.web.servlet.mvc.method.annotation;
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
implements ApplicationContextAware, InitializingBean {
// ...
}
⚠️:可以回到【全局异常处理器的类型】的图看看,ExceptionHandlerExceptionResolver其实就是全局异常处理器HandlerExceptionResolver的子类
它实现了 InitializingBean,重写了afterPropertiesSet(这个方法会在bean初始化完之后执行)
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBodyAdvice beans
initExceptionHandlerAdviceCache();
// ...
}
initExceptionHandlerAdviceCache 会把所有使用了@ControllerAdvice 的bean找到并把它存在自己的参数里面
private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<>(); private void initExceptionHandlerAdviceCache() { if (getApplicationContext() == null) { return; } // 找到所有使用了 @ControllerAdvice 的bean List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); for (ControllerAdviceBean adviceBean : adviceBeans) { Class<?> beanType = adviceBean.getBeanType(); if (beanType == null) { throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean); } // 解析全部的 ExceptionHandler 注解 ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); if (resolver.hasExceptionMappings()) { // 存入当前的类参数里面 this.exceptionHandlerAdviceCache.put(adviceBean, resolver); } if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) { this.responseBodyAdvice.add(adviceBean); } } // ... }
org.springframework.web.method.ControllerAdviceBean#findAnnotatedBeans
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) { ListableBeanFactory beanFactory = context; if (context instanceof ConfigurableApplicationContext) { // Use internal BeanFactory for potential downcast to ConfigurableBeanFactory above beanFactory = ((ConfigurableApplicationContext) context).getBeanFactory(); } List<ControllerAdviceBean> adviceBeans = new ArrayList<>(); // 遍历所有的bean for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Object.class)) { if (!ScopedProxyUtils.isScopedTarget(name)) { // 找到符合的bean ControllerAdvice controllerAdvice = beanFactory.findAnnotationOnBean(name, ControllerAdvice.class); if (controllerAdvice != null) { // 存起来 adviceBeans.add(new ControllerAdviceBean(name, beanFactory, controllerAdvice)); } } } // 排序 OrderComparator.sort(adviceBeans); return adviceBeans; }
配合@ControllerAdvice 注解的通常是 @ExceptionHandler 它用来制定具体的异常,把所有的 ExceptionHandler都存入了 mappedMethods 中org.springframework.web.method.annotation.ExceptionHandlerMethodResolver#ExceptionHandlerMethodResolver
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
}
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
Method oldMethod = this.mappedMethods.put(exceptionType, method);
if (oldMethod != null && !oldMethod.equals(method)) {
throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
exceptionType + "]: {" + oldMethod + ", " + method + "}");
}
}
至此@ControllerAdvice的解析完成
一层层去看调用关系,最终会执行的是 (这个很简单直接去看即可)org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#doResolveHandlerMethodException
执行过程就是循环exceptionHandlerAdviceCache中的每一个全局拦截器,再循环每个拦截器里面的mappedMethods看哪个可以匹配上,就执行哪个
大多数情况下我们会自定义返回值code,比如未鉴权,返回给前端HTTP状态码是200,code为401,但在某些情况下也会直接返回HTTP状态码401,可以使用 @ResponseStatus
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(Exception.class)
public ResultObj bizExceptionHandler(Exception e) {
log.info("全局异常拦截", e);
return ResultObj.success();
}
springframework 里面提供了一个Ordered 接口,实现它重写里面 getOrder 方法就可以进行排序了
并不是系统任何异常都会被它所拦截,因为我们已经知道它的执行点是在MVC的流程中,所以就只有HTTP异常才会被拦截处理
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。