@ControllerAdvice注解作用及源码解析

一、概述

在Spring里,我们可以使用@ControllerAdvice来声明一些全局性的东西,其用法主要有以下三点:

1、@ExceptionHandler注解标注的方法:用于捕获Controller中抛出的不同类型的异常,从而达到异常全局处理的目的;
2、@ModelAttribute注解标注的方法:表示此方法会在执行目标Controller方法之前执行;
3、@InitBinder注解标注的方法:用于请求中注册自定义参数的解析,从而达到自定义请求参数格式的目的;

在我们日常的开发过程中,通常会根据业务定义属于自己的异常,所以通过定制自己的异常处理器,来处理项目中大大小小、各种各样的异常。目前最常用的方式应该是使用@ControllerAdvice+@ExceptionHandler的组合来实现。

二、示例

@ControllerAdvice
@ResponseBody
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ExceptionControllerAdvice {

    @ExceptionHandler(BaseException.class)
    public ResultVO baseException(BaseException ex) {
        return new ResultVO<>(ex.getCode(), ex.getMsg());
    }

    @ExceptionHandler({Exception.class})
    public ResultVO exception(Exception ex, HttpServletRequest request) {
        this.log.error("未处理到异常", ex);
        return getErrorResult(EnumResultMsg.SYSTEM_ERROR, (String)null);
    }
} 
   
@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/exception")
    public void testException() {
        throw new BusinessException("异常测试");
    }
}

BusinessException继承自BaseException,业务逻辑中抛出异常,最终会走到ExceptionControllerAdvice中的baseException方法中,最终封装成ResultVO对象返回给前台

三、源码解析

1、初始化流程

1.1、DispatcherServlet#initStrategies

我们直接看初始化时的核心代码,DispatcherServlet类中的initStrategies

//DispatcherServlet
protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
	//异常处理组件,重点看这个
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

1.2、DispatcherServlet#initHandlerExceptionResolvers

我们重点看第8行initHandlerExceptionResolvers方法

//DispatcherServlet
private void initHandlerExceptionResolvers(ApplicationContext context) {
    this.handlerExceptionResolvers = null;

    if (this.detectAllHandlerExceptionResolvers) {
        // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
        Map matchingBeans = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
            // We keep HandlerExceptionResolvers in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
        }
    }
    else {
        try {
            HandlerExceptionResolver her =
                    context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
            this.handlerExceptionResolvers = Collections.singletonList(her);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, no HandlerExceptionResolver is fine too.
        }
    }

    // Ensure we have at least some HandlerExceptionResolvers, by registering
    // default HandlerExceptionResolvers if no other resolvers are found.
    if (this.handlerExceptionResolvers == null) {
        this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }
}

该方法是找到所有HandlerExceptionResolver接口的实例,并保存到handlerExceptionResolvers中,其中有一个类型是ExceptionHandlerExceptionResolver的bean,我们看看它的类图

@ControllerAdvice注解作用及源码解析_第1张图片

1.3、ExceptionHandlerExceptionResolver#afterPropertiesSet

可以看到实现了InitializingBean接口,看过Spring源码的都应该知道,实现了InitializingBean接口的bean在初始化过程中会调用其afterPropertiesSet方法,我们看下

//ExceptionHandlerExceptionResolver
@Override
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBodyAdvice beans
    initExceptionHandlerAdviceCache();

    if (this.argumentResolvers == null) {
        List resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.returnValueHandlers == null) {
        List handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}

重点看第5行initExceptionHandlerAdviceCache方法

1.4、ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache

//ExceptionHandlerExceptionResolver
private void initExceptionHandlerAdviceCache() {
    if (getApplicationContext() == null) {
        return;
    }

    //找到所有@ControllerAdvice注解的bean,并封装成ControllerAdviceBean对象
    List adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
    //排序
    AnnotationAwareOrderComparator.sort(adviceBeans);

    for (ControllerAdviceBean adviceBean : adviceBeans) {
        Class beanType = adviceBean.getBeanType();
        if (beanType == null) {
            throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
        }
        //把bean的类型封装成一个解析器,会把异常和对应的处理方法放到mappedMethods中
        ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
        if (resolver.hasExceptionMappings()) {
            //把bean和对应的解析器放到exceptionHandlerAdviceCache里
            this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
        }
        if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
            this.responseBodyAdvice.add(adviceBean);
        }
    }

    if (logger.isDebugEnabled()) {
        int handlerSize = this.exceptionHandlerAdviceCache.size();
        int adviceSize = this.responseBodyAdvice.size();
        if (handlerSize == 0 && adviceSize == 0) {
            logger.debug("ControllerAdvice beans: none");
        }
        else {
            logger.debug("ControllerAdvice beans: " +
                    handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
        }
    }
}

这一步找到所有@ControllerAdvice注解的bean,并封装成解析器,放到exceptionHandlerAdviceCache里,记住这个exceptionHandlerAdviceCache,会在真正业务调用时用到

2、业务调用流程

2.1、DispatcherServlet#doDispatch

我们从前端控制器DispatcherServlet的核心方法doDispatch开始看

//DispatcherServlet
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 {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        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()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

我们看54行,这里已经完成的业务的调用,如果系统出现异常,会被捕获,赋值到dispatchException,最后传入processDispatchResult方法,我们重点看下processDispatchResult方法

2.2、DispatcherServlet#processDispatchResult

//DispatcherServlet
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 {
            //如果不是ModelAndViewDefiningException的实例
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            //继续异常处理
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isTraceEnabled()) {
            logger.trace("No view rendering, null ModelAndView returned.");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

可以看到9~22行是存在异常时,对异常的处理,重点看下19行processHandlerException方法

2.3、DispatcherServlet#processHandlerException

//DispatcherServlet
@Nullable
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;
    //这个handlerExceptionResolvers就是我们初始化时设置的
    if (this.handlerExceptionResolvers != null) {
        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            //这里遍历每一个HandlerExceptionResolver,尝试去处理异常,结果如果不为空,表示处理成功
            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);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Using resolved error view: " + exMv);
        }
        WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
        return exMv;
    }

    throw ex;
}

看第10~20行,可以看到通过遍历每一个HandlerExceptionResolver去尝试处理异常,结果如果不为空,表示处理成功,这里的handlerExceptionResolvers有两个,实际处理的是HandlerExceptionResolverComposite,我们继续进去看

@ControllerAdvice注解作用及源码解析_第2张图片

2.4、HandlerExceptionResolverComposite#resolveException

//HandlerExceptionResolverComposite
@Override
@Nullable
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
        @Nullable Object handler,Exception ex) {

    if (this.resolvers != null) {
        for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
            //这里遍历每一个HandlerExceptionResolver,尝试去处理异常,结果如果不为空,表示处理成功
            ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
            if (mav != null) {
                return mav;
            }
        }
    }
    return null;
}

这里和上一步一样,遍历每一个HandlerExceptionResolver,尝试去处理异常,结果如果不为空,表示处理成功,这里的HandlerExceptionResolver有三个,我们看下ExceptionHandlerExceptionResolver的resolveException方法

@ControllerAdvice注解作用及源码解析_第3张图片

我们发现ExceptionHandlerExceptionResolver的resolveException方法在其父类AbstractHandlerExceptionResolver里,继续进去

@ControllerAdvice注解作用及源码解析_第4张图片

2.5、AbstractHandlerExceptionResolver#resolveException

//AbstractHandlerExceptionResolver
@Override
@Nullable
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 warn message when warn logger is not enabled...
            if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
                logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
            }
            // warnLogger with full stack trace (requires explicit config)
            logException(ex, request);
        }
        return result;
    }
    else {
        return null;
    }
}

继续第9行doResolveException进去,到AbstractHandlerMethodExceptionResolver的doResolveException方法中

2.6、AbstractHandlerMethodExceptionResolver#doResolveException

//AbstractHandlerMethodExceptionResolver
@Override
@Nullable
protected final ModelAndView doResolveException(
        HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

    return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
}

继续点击doResolveHandlerMethodException进去,最终来到ExceptionHandlerExceptionResolver的doResolveHandlerMethodException方法

2.7、ExceptionHandlerExceptionResolver#doResolveHandlerMethodException

//ExceptionHandlerExceptionResolver
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
        HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {

    //这一步是核心方法,获取处理异常的HandlerMethod,也就是我们一开始配置的方法
    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();

    try {
        if (logger.isDebugEnabled()) {
            logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
        }
        Throwable cause = exception.getCause();
        if (cause != null) {
            // Expose cause as provided argument as well
            exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
        }
        else {
            // Otherwise, just the given exception as-is
            exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
        }
    }
    catch (Throwable invocationEx) {
        // Any other than the original exception is unintended here,
        // probably an accident (e.g. failed assertion or the like).
        if (invocationEx != exception && logger.isWarnEnabled()) {
            logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
        }
        // Continue with default processing of the original exception...
        return null;
    }

    if (mavContainer.isRequestHandled()) {
        return new ModelAndView();
    }
    else {
        ModelMap model = mavContainer.getModel();
        HttpStatus status = mavContainer.getStatus();
        ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
        mav.setViewName(mavContainer.getViewName());
        if (!mavContainer.isViewReference()) {
            mav.setView((View) mavContainer.getView());
        }
        if (model instanceof RedirectAttributes) {
            Map flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
        return mav;
    }
}

第8行是核心方法,获取处理异常的HandlerMethod,也就是我们一开始配置的方法,点进去

2.8、ExceptionHandlerExceptionResolver#getExceptionHandlerMethod

//ExceptionHandlerExceptionResolver
@Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
        @Nullable HandlerMethod handlerMethod, Exception exception) {

    Class handlerType = null;

    if (handlerMethod != null) {
        //先获取局部异常处理对象(就是在当前请求所在Controller类中的@ExceptionHandler对象)
        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());
        }
    }

	//遍历当前容器内controllerAdvice的集合,其实只有一个controllerAdvice,就是我们前面配置的全局异常处理器
    //exceptionHandlerAdviceCache就是之前初始化的过程中设置的
    for (Map.Entry 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;
}

getExceptionHandlerMethod方法可以分为以下几个步骤

  • 先获取局部异常处理对象(就是在当前请求所在Controller类中的@ExceptionHandler对象)

  • 如果没找到,再通过全局异常处理器去处理

我们重点看36行resolveMethod方法是怎么获取匹配的异常处理的,点击进到ExceptionHandlerMethodResolver类中,一直往下,来到getMappedMethod这个重要的方法中

2.9、ExceptionHandlerMethodResolver#getMappedMethod

@Nullable
private Method getMappedMethod(Class exceptionType) {
    List> matches = new ArrayList<>();
    //遍历该解析器下对应的异常处理方法的key,mappedMethods在初始化的时候会赋值
    for (Class mappedException : this.mappedMethods.keySet()) {
        //判断遍历的异常是否等于传入的异常,或者是其父类、超类
        if (mappedException.isAssignableFrom(exceptionType)) {
            //是的话,加入到集合里
            matches.add(mappedException);
        }
    }
    if (!matches.isEmpty()) {
        //根据自定义的排序规则进行排序
        matches.sort(new ExceptionDepthComparator(exceptionType));
        //排序后取第一条
        return this.mappedMethods.get(matches.get(0));
    }
    else {
        return null;
    }
}

getMappedMethod方法可以分为以下几个步骤

  • 遍历mappedMethods(异常和处理方法的映射关系),找到匹配的处理方法

  • 如果找到了匹配的方法,按照异常的深度排序,取出第一个返回

到此,我们走完所有流程

你可能感兴趣的:(源码学习,Spring,spring,源代码管理,java)