美文网首页Java架构技术栈Java 杂谈Java高级架构
SpringBoot源码解析-ExceptionHandler处

SpringBoot源码解析-ExceptionHandler处

作者: 若丨寒 | 来源:发表于2019-04-10 18:14 被阅读2次

在项目中,经常会使用ExceptionHandler来作为全局性的异常处理中心。那么ExceptionHandler处理异常的原理是什么呢,今天就来分析一下。

ExceptionHandler使用示例

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(value = RuntimeException.class)
    public String handle(){
        return "error";
    }
}

使用还是很简单的,在类上面添加ControllerAdvice注解,在方法上面添加ExceptionHandler注解,就可以在方法里处理相应的异常信息了。

原理剖析

ControllerAdvice和ExceptionHandler注解的作用

异常处理的核心类是ExceptionHandlerExceptionResolver,进入该类。查看afterPropertiesSet方法。

    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBodyAdvice beans
        initExceptionHandlerAdviceCache();
        ...
    }

    private void initExceptionHandlerAdviceCache() {
        if (getApplicationContext() == null) {
            return;
        }
        //这行代码会找出所有标记了ControllerAdvice注解的类
        List<ControllerAdviceBean> 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);
            }
            //遍历这些类,找出有ExceptionHandler注解标注的方法。
            ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
            if (resolver.hasExceptionMappings()) {
                this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
            }
            if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
                this.responseBodyAdvice.add(adviceBean);
            }
        }
        ...
    }

通过上述代码可以看出,在ExceptionHandlerExceptionResolver类中,该类扫描了所有标注有ExceptionHandler注解的方法,并将他们存入了exceptionHandlerAdviceCache中。

异常处理的原理

看过了ControllerAdvice和ExceptionHandler注解的作用后,我们来看一下异常处理的原理。进入DispatcherServlet的doDispatch方法

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
                ...
                //处理controller方法
                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);
        }
        ...
    }

从doDispatch方法中可以看出,程序先处理了controller层的业务逻辑,对于业务逻辑抛出的异常,程序统一做了封装,然后进入了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) {
            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);
            }
        }
        ...
    }

    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) {
        //遍历handlerExceptionResolvers处理异常信息
            for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
                exMv = resolver.resolveException(request, response, handler, ex);
                if (exMv != null) {
                    break;
                }
            }
        }
        ...
    }

那这边的handlerExceptionResolvers是哪里来的呢?

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

        if (this.detectAllHandlerExceptionResolvers) {
            // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
            Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
                    .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
            ...
        }
        ...
    }
复制代码

在DispatcherServlet初始化的时候,会去容器中找HandlerExceptionResolver类型的类。而刚刚的ExceptionHandlerExceptionResolver类就是继承了HandlerExceptionResolver接口,所以这个地方就将他放入了DispatcherServlet中。所以上面的遍历handlerExceptionResolvers处理异常信息的地方,就是调用了ExceptionHandlerExceptionResolver的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);
            ...
        }
    }

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

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

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

        ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
        ...
            else {
                // Otherwise, just the given exception as-is
                exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
            }
        }
        ...
    }

    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        ...
    }

    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        //获取方法的参数
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace("Arguments: " + Arrays.toString(args));
        }
        //执行方法
        return doInvoke(args);
    }

整个异常的执行逻辑如上面的代码,简单点说就是找到相应的异常处理方法,执行他。这个地方getMethodArgumentValues里面的逻辑和 SpringBoot源码解析-controller层参数的封装 是一样的,但是他们能处理的参数类型却不一样。

查看ExceptionHandlerExceptionResolver类的afterPropertiesSet方法:

    public void afterPropertiesSet() {
        ...
        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        ...
    }

    protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

        // Annotation-based argument resolution
        resolvers.add(new SessionAttributeMethodArgumentResolver());
        resolvers.add(new RequestAttributeMethodArgumentResolver());

        // Type-based argument resolution
        resolvers.add(new ServletRequestMethodArgumentResolver());
        resolvers.add(new ServletResponseMethodArgumentResolver());
        resolvers.add(new RedirectAttributesMethodArgumentResolver());
        resolvers.add(new ModelMethodProcessor());

        // Custom arguments
        if (getCustomArgumentResolvers() != null) {
            resolvers.addAll(getCustomArgumentResolvers());
        }

        return resolvers;
    }

这边就是ExceptionHandler方法中可以接收的参数类型了。看一下,要比controller那边的类型少了许多,使用的时候注意一下即可。

相关文章

网友评论

    本文标题:SpringBoot源码解析-ExceptionHandler处

    本文链接:https://www.haomeiwen.com/subject/wfvoiqtx.html