zoukankan      html  css  js  c++  java
  • Spring MVC源码(四) ----- 统一异常处理原理解析

    SpringMVC除了对请求URL的路由处理特别方便外,还支持对异常的统一处理机制,可以对业务操作时抛出的异常,unchecked异常以及状态码的异常进行统一处理。SpringMVC既提供简单的配置类,也提供了细粒度的异常控制机制。

    SpringMVC中所有的异常处理通过接口HandlerExceptionResolver来实现,接口中只定义了一个方法

    public interface HandlerExceptionResolver {
    
        ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
    }

    方法中接受request和response信息,以及当前的处理Handler,和抛出的异常对象。并且提供抽象类AbstractHandlerExceptionResolver,实现resolveException方法,支持前置判断和处理,将实际处理抽象出doResolveException方法由子类来实现。

    @ControllerAdvice和@ExceptionHandler的简单使用

    @ControllerAdvice
    public class ExceptionAdvice {
    
        @ExceptionHandler({ArrayIndexOutOfBoundsException.class})
        @ResponseBody
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        public ResponseDTO handleArrayIndexOutOfBoundsException(ArrayIndexOutOfBoundsException e) {
            // TODO 记录log日志
            e.printStackTrace();
            ResponseDTO responseDTO = new ResponseDTO();
            responseDTO.wrapResponse(ServiceCodeEnum.E999997, "数组越界异常");
    
            return responseDTO;
        }
    
        @ExceptionHandler(value = ParamException.class)
        @ResponseBody
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        public ResponseDTO handleParamException(ParamException e) {
            // TODO 记录log日志
            e.printStackTrace();
            ResponseDTO responseDTO = new ResponseDTO();
            responseDTO.wrapResponse(ServiceCodeEnum.E999998, "输入参数错误");
    
            return responseDTO;
        }
      
        @ExceptionHandler({Exception.class})
        @ResponseBody
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        public ResponseDTO handleException(Exception e) {
            // TODO 记录log日志
            e.printStackTrace();
            ResponseDTO responseDTO = new ResponseDTO();
            responseDTO.wrapResponse(ServiceCodeEnum.E999999, "未知异常");
            return responseDTO;
        }
    
    }

    我们看看 @ControllerAdvice

    @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 {};
    }
    ControllerAdvice 被 @Component 修饰,则说明标记 @ControllerAdvice 会被扫描到容器中

    Spring mvc 的配置如下(这里用到了mvc:annotation-driven):

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core"
        xmlns:p="http://cxf.apache.org/policy" xmlns:ss="http://www.springframework.org/schema/security"
        xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:jee="http://www.springframework.org/schema/jee"
        xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 
        http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd 
        http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd 
        http://cxf.apache.org/policy http://cxf.apache.org/schemas/policy.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
        http://cxf.apache.org/bindings/soap http://cxf.apache.org/schemas/configuration/soap.xsd 
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd 
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    
        <context:component-scan
            base-package="frame.web.controller;frame.web.advice" />
    
        <!--===================== view resovler ===================== -->
        <bean id="jstlViewResolver"
            class="org.springframework.web.servlet.view.UrlBasedViewResolver">
            <property name="order" value="1" />
            <property name="viewClass"
                value="org.springframework.web.servlet.view.JstlView" />
            <property name="prefix" value="/WEB-INF/jsp/" />
        </bean>
    
        <mvc:annotation-driven/>
    
    
        <!-- 自定义参数转换 -->
        <bean id="conversionService"
            class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        </bean>
    
    </beans>
    AnnotationDrivenBeanDefinitionParser类就是用于解析<mvc:annotation-drive>标签的。下面是AnnotationDrivenBeanDefinitionParser的部分源码:
    package org.springframework.web.servlet.config;
    
    class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
    
        /**
        *parse是这个类的核心方法,它用于解析 annotation-drive标签里的内容,根据标签里的内容往spring ioc容器里注入具体的对象。
        **/
        @Override
        public BeanDefinition parse(Element element, ParserContext parserContext) {
            Object source = parserContext.extractSource(element);
            XmlReaderContext readerContext = parserContext.getReaderContext();
    
            CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
            parserContext.pushContainingComponent(compDefinition);
    
            RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);
            //这里有我们熟悉的RequestMappingHandlerMapping,
            RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
            handlerMappingDef.setSource(source);
            handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            handlerMappingDef.getPropertyValues().add("order", 0);
            handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
    
            if (element.hasAttribute("enable-matrix-variables")) {
                Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
                handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
            }
            else if (element.hasAttribute("enableMatrixVariables")) {
                Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enableMatrixVariables"));
                handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
            }
    
            configurePathMatchingProperties(handlerMappingDef, element, parserContext);
            readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME , handlerMappingDef);
    
            RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);
            handlerMappingDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);
    
            //这里会注入具体的ConversionService用于将json,xml转成Spring mvc里的请求和返回对象
            RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
    
            RuntimeBeanReference validator = getValidator(element, source, parserContext);
            RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);
    
            RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
            bindingDef.setSource(source);
            bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            bindingDef.getPropertyValues().add("conversionService", conversionService);
            bindingDef.getPropertyValues().add("validator", validator);
            bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);
    
            ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);
            ManagedList<?> argumentResolvers = getArgumentResolvers(element, parserContext);
            ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, parserContext);
            String asyncTimeout = getAsyncTimeout(element);
            RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);
            ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);
            ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);
            //RequestMappingHandlerAdapter也会在这里注入
            RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
            handlerAdapterDef.setSource(source);
            handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
            handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
            handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
            addRequestBodyAdvice(handlerAdapterDef);
            addResponseBodyAdvice(handlerAdapterDef);
    
            if (element.hasAttribute("ignore-default-model-on-redirect")) {
                Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
                handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
            }
            else if (element.hasAttribute("ignoreDefaultModelOnRedirect")) {
                // "ignoreDefaultModelOnRedirect" spelling is deprecated
                Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignoreDefaultModelOnRedirect"));
                handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
            }
    
            if (argumentResolvers != null) {
                handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
            }
            if (returnValueHandlers != null) {
                handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
            }
            if (asyncTimeout != null) {
                handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
            }
            if (asyncExecutor != null) {
                handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
            }
    
            handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
            handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
            readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME , handlerAdapterDef);
    
            String uriCompContribName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
            RootBeanDefinition uriCompContribDef = new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
            uriCompContribDef.setSource(source);
            uriCompContribDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
            uriCompContribDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
            readerContext.getRegistry().registerBeanDefinition(uriCompContribName, uriCompContribDef);
    
            RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
            csInterceptorDef.setSource(source);
            csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
            RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
            mappedCsInterceptorDef.setSource(source);
            mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
            mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
            String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedCsInterceptorDef);
    
            //这里有我们需要找的ExceptionHandlerExceptionResolver,
            RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
            exceptionHandlerExceptionResolver.setSource(source);
            exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
            exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
            exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
            addResponseBodyAdvice(exceptionHandlerExceptionResolver);
    
            if (argumentResolvers != null) {
                exceptionHandlerExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
            }
            if (returnValueHandlers != null) {
                exceptionHandlerExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
            }
    
            String methodExceptionResolverName = readerContext.registerWithGeneratedName(exceptionHandlerExceptionResolver);
    
            RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
            responseStatusExceptionResolver.setSource(source);
            responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            responseStatusExceptionResolver.getPropertyValues().add("order", 1);
            String responseStatusExceptionResolverName =
                    readerContext.registerWithGeneratedName(responseStatusExceptionResolver);
    
            RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
            defaultExceptionResolver.setSource(source);
            defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            defaultExceptionResolver.getPropertyValues().add("order", 2);
            String defaultExceptionResolverName =
                    readerContext.registerWithGeneratedName(defaultExceptionResolver);
    
            parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
            parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
            parserContext.registerComponent(new BeanComponentDefinition(uriCompContribDef, uriCompContribName));
            parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
            parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
            parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
            parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
    
            // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
            MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
    
            parserContext.popAndRegisterContainingComponent();
    
            return null;
        }
    }
    通过上面代码的分析, 我们可以找到ExceptionHandlerExceptionResolver这个类来用于处理Spring MVC的各种异常,那ExceptionHandlerExceptionResolver具体又是如何跟ControllerAdvice配合使用来处理各种异常的呢?我们来看看ExceptionHandlerExceptionResolver里的关键代码:
    package org.springframework.web.servlet.mvc.method.annotation;
    
    //我们考到这个类实现了InitializingBean,则容器初始化的时候在实例化此Bean后会调用afterPropertiesSet()
    public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
            implements ApplicationContextAware, InitializingBean {
        //这里有个map用于保存ControllerAdviceBean和ExceptionHandlerMethodResolver
        private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
                new LinkedHashMap<ControllerAdviceBean, ExceptionHandlerMethodResolver>();
    
        //这个方法是由spring 容器调用的
        @Override
        public void afterPropertiesSet() {
            // Do this first, it may add ResponseBodyAdvice beans
            //这个方法里会处理ExceptionHandler
            initExceptionHandlerAdviceCache();
    
            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);
            }
        }
    
        /**
        *这个方法里会在spring ioc容器里找出标注了@ControllerAdvice的类,如果有方法标注了@ExceptionHandler会生成一个ExceptionHandlerMethodResolver类用于处理异常并放到exceptionHandlerAdviceCache这个map缓存类里。
        **/
        private void initExceptionHandlerAdviceCache() {
            if (getApplicationContext() == null) {
                return;
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Looking for exception mappings: " + getApplicationContext());
            }
            //这里会找到容器里标注了@ControllerAdvice注解的类
            List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
            AnnotationAwareOrderComparator.sort(adviceBeans);
    
            for (ControllerAdviceBean adviceBean : adviceBeans) {
                //这个构造方法里会检查ControllerAdvice类里是否有@ExceptionHandler标注的方法,在ExceptionHandlerMethodResolver 有个异常的map。
                //在ExceptionHandlerMethodResolver构造器中会通过反射拿到所有标注@ExceptionHandler的方法并加入ExceptionHandlerMethodResolver的map中
                //key为  @ExceptionHandler(value = ParamException.class) 标注的value,这里就是ParamException.class,值为标注@ExceptionHandler的Method
                ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());
                if (resolver.hasExceptionMappings()) {
                    //如果有@ExceptionHandler方法,会执行下面的逻辑
                    //将标注@ControllerAdvice的类Bean,和此Bean中封装了所有Exception为key,Method为value的Map的ExceptionHandlerMethodResolver对象加入到exceptionHandlerAdviceCache的缓存中
                    this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
                    if (logger.isInfoEnabled()) {
                        logger.info("Detected @ExceptionHandler methods in " + adviceBean);
                    }
                }
                if (ResponseBodyAdvice.class.isAssignableFrom(adviceBean.getBeanType())) {
                    this.responseBodyAdvice.add(adviceBean);
                    if (logger.isInfoEnabled()) {
                        logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean);
                    }
                }
            }
        }
    
        /**
        ** 这个方法会根据exceptionHandlerAdviceCache这个找到具体需要处理异常的方法,这个后面再讲
        */
        protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
            Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
    
            if (handlerMethod != null) {
                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);
                }
            }
    
            for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
                if (entry.getKey().isApplicableToBeanType(handlerType)) {
                    ExceptionHandlerMethodResolver resolver = entry.getValue();
                    //根据具体的异常找到处理异常的方法,然后调用
                    Method method = resolver.resolveMethod(exception);
                    if (method != null) {
                        return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
                    }
                }
            }
    
            return null;
        }
    }

    我们来看看 ExceptionHandlerMethodResolver这个类

    public class ExceptionHandlerMethodResolver {
        public static final MethodFilter EXCEPTION_HANDLER_METHODS = new MethodFilter() {
            public boolean matches(Method method) {
                return AnnotationUtils.findAnnotation(method, ExceptionHandler.class) != null;
            }
        };
        private static final Method NO_METHOD_FOUND = ClassUtils.getMethodIfAvailable(System.class, "currentTimeMillis", new Class[0]);
        //此缓存Map存放了@ControllerAdvice中所有注解了@ExceptionHandler的方法,其中@ExceptionHandler的value也就是Exception做为Key,值为当前Method
        private final Map<Class<? extends Throwable>, Method> mappedMethods = new ConcurrentHashMap(16);
        private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentHashMap(16);
    
        public ExceptionHandlerMethodResolver(Class<?> handlerType) {
            //通过反射拿到当前Class的所有方法,也就是标注了@ControllerAdvice的所有方法
            Iterator var2 = MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS).iterator();
    
            //遍历所有的方法,寻找标注了@ExceptionHandler的方法
            while(var2.hasNext()) {
                Method method = (Method)var2.next();
                //这里获取到标注了@ExceptionHandler的方法上所有的@ExceptionHandler中的value
                //如{ArrayIndexOutOfBoundsException.class,ParamException.calss}
                Iterator var4 = this.detectExceptionMappings(method).iterator();
    
                while(var4.hasNext()) {
                    Class<? extends Throwable> exceptionType = (Class)var4.next();
                    //将ArrayIndexOutOfBoundsException.class作为key,method做为value加入到map缓存中
                    this.addExceptionMapping(exceptionType, method);
                }
            }
    
        }
        
        private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
            List<Class<? extends Throwable>> result = new ArrayList();
            //这里获取到标注了@ExceptionHandler的方法上所有的@ExceptionHandler中的value
            //如{ArrayIndexOutOfBoundsException.class,ParamException.calss}
            this.detectAnnotationExceptionMappings(method, result);
            if (result.isEmpty()) {
                Class[] var3 = method.getParameterTypes();
                int var4 = var3.length;
    
                for(int var5 = 0; var5 < var4; ++var5) {
                    Class<?> paramType = var3[var5];
                    if (Throwable.class.isAssignableFrom(paramType)) {
                        result.add(paramType);
                    }
                }
            }
    
            Assert.notEmpty(result, "No exception types mapped to {" + method + "}");
            return result;
        }
        
        protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
            //判断此方法是否标记@ExceptionHandler,如果没有则返回null,如果有标记则返回ExceptionHandler
            ExceptionHandler ann = (ExceptionHandler)AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
            //获取ExceptionHandler注解的所有值,这里是一个数组,有可能有多个,如@ExceptionHandler({ArrayIndexOutOfBoundsException.class,ParamException.calss})
            result.addAll(Arrays.asList(ann.value()));
        }
    
    }

    异常处理原理

    SpringMVC怎么在请求处理的过程中完成对异常的统一处理的呢?我们从源码来深度解读。

    回到DispatcherServlet的doDispatcher方法

    try {
        processedRequest = checkMultipart(request);
        multipartRequestParsed = (processedRequest != request);
    
        // Determine handler for the current request.
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null || mappedHandler.getHandler() == null) {
            noHandlerFound(processedRequest, response);
            return;
        }
    
        // Determine handler adapter for the current request.
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }
    
        // Actually invoke the handler.
        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);

    可以看到对请求处理的核心处理使用一个大的try/catch,如果出现异常,统一封装成dispatchException交给processDispatchResult方法进行处理。我们知道processDispatchResult方法用来对返回视图进行操作,而同时也对异常进行统一处理。

    在processDispatchResult中,首先对异常进行判断。

    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);
        }
    }

    如果不是特殊的ModelAndViewDefiningException,则由processHandlerException来操作。

    protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
            Object handler, Exception ex) throws Exception {
    
        // Check registered HandlerExceptionResolvers...
        ModelAndView exMv = null;
        // 遍历所有注册的异常处理器,由异常处理器进行处理
        for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
            exMv = handlerExceptionResolver.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()) {
                exMv.setViewName(getDefaultViewName(request));
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
            }
            WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
            return exMv;
        }
    
        throw ex;
    }

    我们主要关注异常处理器对异常的处理,SpringMVC通过HandlerExceptionResolver的resolveException调用实现类的实际实现方法doResolveException。

    ExceptionHandlerExceptionResolver

    ExceptionHandlerExceptionResolver支持了@ExceptionHandler注解的实现。它的抽象基类AbstractHandlerMethodExceptionResolver继承了AbstractHandlerExceptionResolver,doResolveException方法实际调用ExceptionHandlerExceptionResolver的doResolveHandlerMethodException方法。

    protected final ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        return this.doResolveHandlerMethodException(request, response, (HandlerMethod)handler, ex);
    }
    
    protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {
    
        // 根据HandlerMethod和exception获取异常处理的Method
        ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
        if (exceptionHandlerMethod == null) {
            return null;
        }
    
        // 设置异常处理方法的参数解析器和返回值解析器
        exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
    
        // 执行异常处理方法
        try {
            if (logger.isDebugEnabled()) {
                logger.debug("Invoking @ExceptionHandler method: " + 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("Failed to invoke @ExceptionHandler method: " + 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<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
                request = webRequest.getNativeRequest(HttpServletRequest.class);
                RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
            }
            return mav;
        }
    }

    我们主要关注的是如何匹配到异常处理方法的,也就是 ExceptionHandlerExceptionResolver中的 getExceptionHandlerMethod

    protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
        Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
    
        // 从当前Controller中匹配异常处理Method,此处我们暂时不分析
        if (handlerMethod != null) {
            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);
            }
        }
    
        // 从ControllerAdvice中匹配异常处理Method
        // 我们知道容器初始化的时候,已经寻找所有标注了@ControllerAdvice的类,并将此类标注了@ExceptionHandler的方法放到当前类的exceptionHandlerAdviceCache中
        //遍历所有的@ControllerAdvice生成的缓存
        for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
            if (entry.getKey().isApplicableToBeanType(handlerType)) {
                //拿到ExceptionHandlerMethodResolver,这个对象里包含了标注为@ExceptionHandler的Key为Excpthion,value为Method的缓存Map
                ExceptionHandlerMethodResolver resolver = entry.getValue();
                //寻找ExceptionHandlerMethodResolver有没有标注@ExceptionHandler能匹配当前异常的方法
                Method method = resolver.resolveMethod(exception);
                if (method != null) {
                    return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
                }
            }
        }
    
        return null;
    }
    
    public Method resolveMethod(Exception exception) {
        Method method = this.resolveMethodByExceptionType(exception.getClass());
        if (method == null) {
            Throwable cause = exception.getCause();
            if (cause != null) {
                method = this.resolveMethodByExceptionType(cause.getClass());
            }
        }
    
        return method;
    }
    
    public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
        //先从exceptionLookupCache缓存中拿,第一次肯定拿不到,因为我们是存在mappedMethods这个缓存中
        Method method = (Method)this.exceptionLookupCache.get(exceptionType);
        if (method == null) {
            method = this.getMappedMethod(exceptionType);
            this.exceptionLookupCache.put(exceptionType, method != null ? method : NO_METHOD_FOUND);
        }
    
        return method != NO_METHOD_FOUND ? method : null;
    }
    
    private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
        List<Class<? extends Throwable>> matches = new ArrayList();
        //拿到所有的Key
        Iterator var3 = this.mappedMethods.keySet().iterator();
    
        while(var3.hasNext()) {
            Class<? extends Throwable> mappedException = (Class)var3.next();
            //判断exceptionType是不是mappedException本身或者其子类
            if (mappedException.isAssignableFrom(exceptionType)) {
                matches.add(mappedException);
            }
        }
    
        if (!matches.isEmpty()) {
            Collections.sort(matches, new ExceptionDepthComparator(exceptionType));
            //返回匹配到异常的Method
            return (Method)this.mappedMethods.get(matches.get(0));
        } else {
            return null;
        }
    }

    匹配到exceptionHandlerMethod后,设置一些方法执行的环境,然后调用ServletInvocableHandlerMethod中的invokeAndHandle去执行,这个调用过程和正常请求的调用就是一致了。

      exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, new Object[]{exception, cause, handlerMethod});

    这里就是调用异常处理的方法,总体来说,就是SpringMvc启动的时候初始化异常处理的组件,将 @ControllerAdvice标记的特殊类和@ExceptionHandler 标记的方法存入缓存中,当目标Controller出现异常的时候,就通过抛出的异常在缓存中找到对应的处理方法,然后去调用对应的异常处理方法就OK了。

  • 相关阅读:
    BZOJ 1066 [SCOI2007]蜥蜴 (最大流)
    Codeforces 1092 D2 Great Vova Wall (Version 2) (栈)
    BZOJ 1046 [HAOI2007]上升序列(LIS + 贪心)
    牛客练习赛34 D little w and Exchange(归纳)
    BZOJ 1042 [HAOI2008]硬币购物(完全背包+容斥)
    GTMD并查集!
    2018icpc南京现场赛-G Pyramid(打标找规律+逆元)
    drwxr-xr-x 2 root root 4096 06-29 14:30 Test 分段解释
    Linux里面非常重要的目录
    点击 触发 事件 的 jQuery 写法样式
  • 原文地址:https://www.cnblogs.com/java-chen-hao/p/11190659.html
Copyright © 2011-2022 走看看