目录
- 准备
- 源码跟踪
- ExceptionHandlerExceptionResolver初始化
- ExceptionHandlerExceptionResolver注入
- ExceptionHandlerExceptionResolver中exceptionHandlerAdviceCache初始化
- ExceptionHandlerMethodResolver中mappedMethods初始化
- 结尾
准备
- 准备一个controller类, 里面抛出一个异常
@RestController
@RequestMapping("/hello")
public class HelloController {@GetMapping("/h")public String hello(HttpServletResponse response) throws ArithmeticException {try {int a = 1 / 0;} catch (Exception e) {throw e;}return "hello";}}
- 配置一个全局异常处理类
/*** 异常处理器*/
@RestControllerAdvice
public class BDExceptionHandler {/*** 参数异常* @param e 异常* @return BaseResult*/@ExceptionHandler(ArithmeticException.class)public R doBaseExceptionHandler(ArithmeticException e) {return R.error(e.getMessage());}
}
- 接下来访问url, 跟踪源码
源码跟踪
进入到mvc的入口类DispatcherServlet的 doDispatch方法, 可以发现整体逻辑被try catch包围, 当第3步抛出异常之后就会被捕获, dispatchException对象则不为空,最后进入processDispatchResult方法进行处理
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 {//1.获取处理器链路//2.执行拦截器前置方法//3.调用controller方法, 返回结果集//4.执行拦截器后置方法}catch (Exception ex) {//dispatchException = ex;}catch (Throwable err) {dispatchException = new NestedServletException("Handler dispatch failed", err);}processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}
进入processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;//判断异常是否为空, 如果不是空则处理if (exception != null) {//处理ModelAndViewDefiningException异常if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {//处理其他所有的异常Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}//省略下面代码
进入mv = processHandlerException(request, response, handler, exception);
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {// Success and error responses may use different content typesrequest.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);// Check registered HandlerExceptionResolvers...ModelAndView exMv = null;if (this.handlerExceptionResolvers != null) {//this.handlerExceptionResolvers里面默认有两个处理异常的类for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {// 遍历,判断使用那个类来处理exMv = resolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}}if (exMv != null) {//省略代码return exMv;}throw ex;}
看一下上面for循环里面的this.handlerExceptionResolvers对象的结构 :
DefaultErrorAttributes : 没做任何处理, 只是将异常设置到request对象的属性当中;
HandlerExceptionResolverComposite : 里面有三个异常处理类, 分别的作用是 :
ExceptionHandlerExceptionResolver:基于 @ExceptionHandler 配置 HandlerMethod 的 HandlerExceptionResolver 实现类。例如通过 @ControllerAdvice 注解自定义异常处理器,加上@ExceptionHandler注解指定方法所需要处理的异常类型
ResponseStatusExceptionResolver:基于 @ResponseStatus 提供错误响应的 HandlerExceptionResolver 实现类。例如在方法上面添加 @ResponseStatus 注解,指定该方法发生异常时,需要设置的 code 响应码和 reason 错误信息
DefaultHandlerExceptionResolver:默认 HandlerExceptionResolver 实现类,针对各种异常,设置错误响应码。例如 HTTP Method 不支持,则在这个实现类中往响应中设置错误码和错误信息
原文链接:https://blog.csdn.net/Running666/article/details/130786561
进入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;}
由于只设置了第一种异常处理方式, 所以异常会由ExceptionHandlerExceptionResolver来处理, 进入handlerExceptionResolver.resolveException()
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {if (shouldApplyTo(request, handler)) {prepareResponse(ex, response);//主要的异常处理逻辑ModelAndView result = doResolveException(request, response, handler, ex);if (result != null) {// Print debug message when warn logger is not enabled.if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));}logException(ex, request);}return result;}else {return null;}}
进入doResolveException(request, response, handler, ex)方法;
@Override@Nullableprotected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {//根据controller类和异常来判断由哪个类来处理这个异常ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);if (exceptionHandlerMethod == null) {return null;}//填充参数处理器if (this.argumentResolvers != null) {exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}//填充返回结果处理器if (this.returnValueHandlers != null) {exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}ServletWebRequest webRequest = new ServletWebRequest(request, response);ModelAndViewContainer mavContainer = new ModelAndViewContainer();ArrayList<Throwable> exceptions = new ArrayList<>();try {//省略代码Object[] arguments = new Object[exceptions.size() + 1];exceptions.toArray(arguments); // efficient arraycopy call in ArrayListarguments[arguments.length - 1] = handlerMethod;//反射调用异常处理类exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);}catch (Throwable invocationEx) {// Any other than the original exception (or a cause) is unintended here,// probably an accident (e.g. failed assertion or the like).if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) {logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);}// Continue with default processing of the original exception...return null;}//省略}
主要逻辑就是看如何找到处理对应异常的方法, 进入getExceptionHandlerMethod(handlerMethod, exception);
进入到了ExceptionHandlerExceptionResolver类中, 在这个类里面有两个关键的属性,exceptionHandlerCache和exceptionHandlerAdviceCache, 两个都是map, exceptionHandlerAdviceCache的初始化下面再说,exceptionHandlerCache的初始化想知道的自己去查一下吧
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =new ConcurrentHashMap<>(64);private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =new LinkedHashMap<>();
exceptionHandlerCache里面存的就是在当前controller里面包含@ExceptionHandler注解的方法, key: controller类, value : 方法包装类
exceptionHandlerAdviceCache里面存的就是标注了@ControllerAdvice注解的类,里面带有@ExceptionHandler注解的方法, key:
@RestControllerAdvice注解标注的类, value: 方法包装类
@Nullableprotected ServletInvocableHandlerMethod getExceptionHandlerMethod(@Nullable HandlerMethod handlerMethod, Exception exception) {Class<?> handlerType = null;//从exceptionHandlerCache获取异常对应的处理方法, 由于没有配置, 肯定获取不到if (handlerMethod != null) {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);}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;}
进入Method method = resolver.resolveMethod(exception);, 最终进入到resolveMethodByExceptionType()方法, 在ExceptionHandlerMethodResolver类中
ExceptionHandlerMethodResolver中有两个关键属性mappedMethods,和exceptionLookupCache, 两个也都是map, 两个都是根据异常获取对应的异常处理方法, mappedMethods在程序启动时候初始化, exceptionLookupCache相当于一个缓存,为了提高查询速度
@Nullablepublic Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {//从缓存中获取处理方法, 如果获取不到,返回null(第一次执行此方法肯定获取不到)Method method = this.exceptionLookupCache.get(exceptionType);if (method == null) {//如果获取不到, 则去mappedMethods获取method = getMappedMethod(exceptionType);//将获取的结果缓存到exceptionLookupCache中this.exceptionLookupCache.put(exceptionType, method);}return (method != NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null);}
进入getMappedMethod(exceptionType);
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {List<Class<? extends Throwable>> matches = new ArrayList<>();//遍历所有可以处理的异常, 判断与抛出的异常是否匹配for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {if (mappedException.isAssignableFrom(exceptionType)) {matches.add(mappedException);}}if (!matches.isEmpty()) {//如果有多个方法可以匹配来处理异常,则进行排序if (matches.size() > 1) {matches.sort(new ExceptionDepthComparator(exceptionType));}//获取第一个方法来处理异常,返回方法return this.mappedMethods.get(matches.get(0));}else {return NO_MATCHING_EXCEPTION_HANDLER_METHOD;}}
到此为止, 已经获取到对应的处理异常的方法了, 在上面的doResolveHandlerMethodException方法中, 最后通过反射调用该方法, 处理异常
接下来讨论一下ExceptionHandlerExceptionResolver类里面的exceptionHandlerCache和exceptionHandlerAdviceCache初始化和
ExceptionHandlerMethodResolver类中的mappedMethods初始化
ExceptionHandlerExceptionResolver初始化
ExceptionHandlerExceptionResolver实现了InitializingBean接口, 猜测初始化肯定在afterPropertiesSet()方法中, 我们至于要查找ExceptionHandlerExceptionResolver是如何注入到springboot容器中的, 及afterPropertiesSet()方法逻辑即可
ExceptionHandlerExceptionResolver注入
实际上,ExceptionHandlerExceptionResolver并不会成为bean交给Spring容器管理。但是在WebMvcConfigurationSupport初始化过程中,会手动调用afterPropertiesSet()进行默认初始化
还记得上面HandlerExceptionResolverComposite类里面有三个异常处理类吗? 看一下他们是如何注入的
在WebMvcConfigurationSupport类里面的handlerExceptionResolver()方法:
@Beanpublic HandlerExceptionResolver handlerExceptionResolver(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();configureHandlerExceptionResolvers(exceptionResolvers);//如果是空, 在下面的if里面将三个异常处理类添加到集合中if (exceptionResolvers.isEmpty()) {addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);}extendHandlerExceptionResolvers(exceptionResolvers);HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();composite.setOrder(0);//赋值属性, 最后返回composite.setExceptionResolvers(exceptionResolvers);return composite;}
进入到addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,ContentNegotiationManager mvcContentNegotiationManager) {//创建ExceptionHandlerExceptionResolver 对象ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);exceptionHandlerResolver.setMessageConverters(getMessageConverters());exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());if (jackson2Present) {exceptionHandlerResolver.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));}if (this.applicationContext != null) {exceptionHandlerResolver.setApplicationContext(this.applicationContext);}//手动调用afterPropertiesSet()给属性初始化exceptionHandlerResolver.afterPropertiesSet();//添加第一个exceptionResolvers.add(exceptionHandlerResolver);ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();responseStatusResolver.setMessageSource(this.applicationContext);//添加第二个exceptionResolvers.add(responseStatusResolver);//添加第三个exceptionResolvers.add(new DefaultHandlerExceptionResolver());}
所以我们只需要看WebMvcConfigurationSupport是如何注入的就可以了, 实际上他是通过WebMvcAutoConfiguration类来间接注入的
而WebMvcAutoConfiguration类的注入是通过自动装配来注册的
ExceptionHandlerExceptionResolver中exceptionHandlerAdviceCache初始化
@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBodyAdvice beansinitExceptionHandlerAdviceCache();if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers == null) {List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}}
进入initExceptionHandlerAdviceCache()方法
private void initExceptionHandlerAdviceCache() {if (getApplicationContext() == null) {return;}//获取带有ControllerAdvice注解的类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);}//构建value对象, 存入到exceptionHandlerAdviceCache中//ExceptionHandlerMethodResolver中mappedMethods初始化逻辑也在其中ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings()) {this.exceptionHandlerAdviceCache.put(adviceBean, resolver);}if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {this.responseBodyAdvice.add(adviceBean);}}}
ExceptionHandlerMethodResolver中mappedMethods初始化
mappedMethods的初始化逻辑很简单, 在上面的initExceptionHandlerAdviceCache()方法中我们已经找到了带有ControllerAdvice的所有类, 将类的类型作为参数创建了ExceptionHandlerMethodResolver对象, mappedMethods的初始化就在构造方法中
进入ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType) 方法
public ExceptionHandlerMethodResolver(Class<?> handlerType) {//获取到handlerType类中所有的带有ExceptionHandler注解的方法,for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {//解析ExceptionHandler注解中的属性, 因为可以配置多个异常类型, 所有返回的是集合, 进行遍历for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {//将异常类型作为key, 方法作为value, 存入到mappedMethods中addExceptionMapping(exceptionType, method);}}}private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {List<Class<? extends Throwable>> result = new ArrayList<>();//获取method上的ExceptionHandler注解的所有的异常类型,存入到result中, 返回detectAnnotationExceptionMappings(method, result);if (result.isEmpty()) {for (Class<?> paramType : method.getParameterTypes()) {if (Throwable.class.isAssignableFrom(paramType)) {result.add((Class<? extends Throwable>) paramType);}}}if (result.isEmpty()) {throw new IllegalStateException("No exception types mapped to " + method);}return result;}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 + "}");}}
结尾
上面已经说了到逻辑代码发生异常之后, 我们自定义的异常处理类如何加载处理这些异常(同时包括系统启动时的一些系统异常), 但是如果抛出一种没有处理类的异常会怎么样呢? 下篇文章在讨论一下