Spring中@ExceptionHandler的工作原理

232 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第27天,点击查看活动详情

前言

在上一篇文章中,我们简单说明了如何使用@RestControllerAdvice@ExceptionHandler一起完成统一异常处理,但是我们目前还不知道@ExceptionHandler到底是如何实现的;在这篇文章中,我们准备对其中的原理一探究竟,以便我们能够更好地理解如何运用这两个注解;

收集@ExceptionHandler注解的方法

我们首先来看一下Spring是如何收集注解了@ExceptionHandler的方法的,我们找到ExceptionHandlerExceptionResolver.afterPropertiesSet()方法,因为ExceptionHandlerExceptionResolver实现了InitializingBean,所以在ExceptionHandlerExceptionResolver初始化完毕后立马会执行afterPropertiesSet()

   public void afterPropertiesSet() {
       // 收集ExceptionHandler方法
       this.initExceptionHandlerAdviceCache();
      ......
  }

我们来看看具体是如何收集的:

   private void initExceptionHandlerAdviceCache() {
       if (this.getApplicationContext() != null) {
           // 找到所有被@ControllerAdvice注解的Bean
           List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(this.getApplicationContext());
           Iterator var2 = adviceBeans.iterator();
           // 遍历所有的Bean
           while(var2.hasNext()) {
               ControllerAdviceBean adviceBean = (ControllerAdviceBean)var2.next();
               Class<?> beanType = adviceBean.getBeanType();
               if (beanType == null) {
                   throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
              }
​
               ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
               // 找到被@ExceptionHandler注解的Bean,并放进exceptionHandlerAdviceCache缓存
               if (resolver.hasExceptionMappings()) {
                   this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
              }
​
               if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
                   this.responseBodyAdvice.add(adviceBean);
              }
          }
​
           if (this.logger.isDebugEnabled()) {
               int handlerSize = this.exceptionHandlerAdviceCache.size();
               int adviceSize = this.responseBodyAdvice.size();
               if (handlerSize == 0 && adviceSize == 0) {
                   this.logger.debug("ControllerAdvice beans: none");
              } else {
                   this.logger.debug("ControllerAdvice beans: " + handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
              }
          }
​
      }
  }

上面源码就做了以下2步:

1.找到所有的ControllerAdvice Bean对象,其实就是被@ControllerAdvice注解的类;

2.遍历所有的ControllerAdvice Bean对象,并找出其中被@ExceptionHandler注解的Bean,因为并不是所有的ControllerAdvice Bean对象都会有@ExceptionHandler注解;

异常拦截处理

前面我们把异常处理方法全部收集好了后,接下来就要开始找到exceptionHandlerAdviceCache在哪里被拿来使用了,我们可以看到DispatcherServlet.doDispatch()方法:

   protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
       try{
          ......
           mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
          ......
      }catch(Exception e){
           dispatchException = e;
      }catch (Throwable t){
           dispatchException = new NestedServletException("Handler dispatch failed", t);
      }
       this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
    ......
  }

上面代码中,ha.handle()就是执行我们的Controller中的目标方法,Controller抛出的任何异常都会被捕捉到,最后把异常给dispatchException赋值,然后再执行processDispatchResult()方法来处理异常:

   private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChainmappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
       boolean errorView = false;
       // 有异常
       if (exception != null) {
           // 如果是返回的ModelAndViewDefiningException,那么直接获取ModelAndView
           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;
          }
      }
    ......
  }

1.如果Controller抛出了ModelAndViewDefiningException,那么直接从里面提取ModelAndView返回;

2.如果抛出的异常不是ModelAndViewDefiningException,那么只能通过异常处理方法来操作;

   protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Objecthandler, Exception ex) throws Exception {
       request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
       ModelAndView exMv = null;
       if (this.handlerExceptionResolvers != null) {
           Iterator var6 = this.handlerExceptionResolvers.iterator();
           // 遍历异常处理器
           while(var6.hasNext()) {
               HandlerExceptionResolver resolver = (HandlerExceptionResolver)var6.next();
               // 调用异常处理方法
               exMv = resolver.resolveException(request, response, handler, ex);
               if (exMv != null) {
                   break;
              }
          }
      }
    ......
  }

小结

至此,我们就通过源码解析的方式了解了@ExceptionHandler的工作原理,它通过ExceptionHandlerExceptionResolver的初始化操作解析了所有被@ExceptionHandler注解的方法,并包装成ExceptionHandlerMethodResolver;另外在Controller被调用后,将根据抛出的异常来选择对应的ExceptionHandlerMethodResolver来处理;