【星海】@ControllerAdvice注解含义以及集中异常处理的源码分析

499 阅读4分钟

全局异常处理的场景

该代码可以在:github.com/TakeatEasy/… 中查看,如果有用的话,麻烦Star一下

首先,我们定义了一个全局异常处理的类

1.PNG 之后,提供了一个接口,能够产生我们祖传的除零异常 2.PNG 最后,访问我们的接口,可以看出,是调用的RuntimeException这个类的处理逻辑 3.PNG

这是一个最简单的集中异常处理的场景,在真正的开发过程中,可以采用这种技术,集中的向前端返回异常的情况。重点在于我们SpringMvc如何实现的这个功能,以及怎么做到的自动应用RuntimeException这个方法,而不是Exception?

全局异常处理的实现

那我们先来看一下这个注解的定义:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<?>[] assignableTypes() default {};

    Class<? extends Annotation>[] annotations() default {};
}

之前的注解基本介绍过了,@Target能够表明当前注解可以标注在哪些对象上,@Retention则是声注解吊桶的时间,这个实在Runtime调用的,感觉这种调用多是一种标注,后续为了其他的反射方法来进行实现的。

接下来我们从Spring Mvc的dispatcher Servlet入口 dodispatch来看一下异常是如何进行处理的

```
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        try {
            ModelAndView mv = null;
            Object dispatchException = null;

            try {
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }

                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                String method = request.getMethod();
                boolean isGet = HttpMethod.GET.matches(method);
                if (isGet || HttpMethod.HEAD.matches(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
```
//根据配置的HandlerAdapter 对handler进行适配
```
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
```
//这里会调用具体的Handler也就是我们写的Controller
```
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                this.applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var20) {
                dispatchException = var20;
            } catch (Throwable var21) {
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }
```
//具体处理异常的逻辑看来是在这个方法里了,具体的逻辑看下面的源码
```
            this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
        } catch (Exception var22) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
        } catch (Throwable var23) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
        }

    } finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else if (multipartRequestParsed) {
            this.cleanupMultipart(processedRequest);
        }

    }
}
```

接下来的processDispatchResult是如何对dispatch的结果进行处理的

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
    boolean errorView = false;
    // 这里是对于exception的执行方法
    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;
            // 这里也是调用handle的exception
            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);
        }

    }
}

// 接下来是processHandleException的入口
```
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, 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;
            }
        }
    }

    if (exMv != null) {
        if (exMv.isEmpty()) {
            request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
            return null;
        } else {
            if (!exMv.hasView()) {
                String defaultViewName = this.getDefaultViewName(request);
                if (defaultViewName != null) {
                    exMv.setViewName(defaultViewName);
                }
            }

            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Using resolved error view: " + exMv, ex);
            } else if (this.logger.isDebugEnabled()) {
                this.logger.debug("Using resolved error view: " + exMv);
            }

            WebUtils.exposeErrorRequestAttributes(request, ex, this.getServletName());
            return exMv;
        }
    } else {
        throw ex;
    }
}
```

这个方法主要是调用所有的异常解析器,看哪一个能够处理当前的异常,因此我们还需要向异常解析器来查看进一步的实现,通过继承的层次,我们可以发现ExceptionHandlerExceptionResolver是该接口的实现类。

image.png

private void initExceptionHandlerAdviceCache() {
    if (this.getApplicationContext() != null) {
    // 查找所有被Controller标注的bean
        List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(this.getApplicationContext());
        Iterator var2 = adviceBeans.iterator();

        while(var2.hasNext()) {
            ControllerAdviceBean adviceBean = (ControllerAdviceBean)var2.next();
            Class<?> beanType = adviceBean.getBeanType();
            if (beanType == null) {
                throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
            }
            // 调用这个方法去解析那个exception方法解析哪一种异常,这个地方也就解释了我们为什么调用的Runtime的解决方法
            ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
            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");
            }
        }

    }
}

那么SpringMvc是如何进行处理我们的ControllerAdvice标注的对象呢 ControllerAdviceBean有个重要的方法findAnnotatedBeans

public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
    ListableBeanFactory beanFactory = context;
    if (context instanceof ConfigurableApplicationContext) {
        beanFactory = ((ConfigurableApplicationContext)context).getBeanFactory();
    }

    List<ControllerAdviceBean> adviceBeans = new ArrayList();
    String[] var3 = BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory)beanFactory, Object.class);
    int var4 = var3.length;

    for(int var5 = 0; var5 < var4; ++var5) {
        String name = var3[var5];
        if (!ScopedProxyUtils.isScopedTarget(name)) {
            ControllerAdvice controllerAdvice = (ControllerAdvice)((ListableBeanFactory)beanFactory).findAnnotationOnBean(name, ControllerAdvice.class);
            if (controllerAdvice != null) {
                adviceBeans.add(new ControllerAdviceBean(name, (BeanFactory)beanFactory, controllerAdvice));
            }
        }
    }

    OrderComparator.sort(adviceBeans);
    return adviceBeans;
}

该方法的核心作用就是用来查找,ApplicationContext中的Bean是否被我们的ControllerAdvice标注,将其封装为一个ControllerAdviceBean对象,到这里我们的解析建立了这个COntrollerAdvice的对象,但是还没有和ExceptionHandler注解建立联系!

啊啊啊啊啊啊啊啊啊啊啊啊,好长啊,这是最后一个类的方法了,我保证!!!!!!!!

接下来看到ExceptionHandlerMethodResolver这个类,会去解析Handler的方法

private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
    // 用于查找所有的标注了ExceptionHandler注解的方法相应的对象
    ExceptionHandler ann = (ExceptionHandler)AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
    Assert.state(ann != null, "No ExceptionHandler annotation");
    result.addAll(Arrays.asList(ann.value()));
}

```
// 将error 映射到对应的解决方法上
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
    Method oldMethod = (Method)this.mappedMethods.put(exceptionType, method);
    if (oldMethod != null && !oldMethod.equals(method)) {
        throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" + exceptionType + "]: {" + oldMethod + ", " + method + "}");
    }
}
```

完结,撒花!