深入Spring:自定义ExceptionHandler

作者: wcong | 来源:发表于2016-05-18 19:52 被阅读3762次

    前言

    上一篇文章介绍了HandlerAdapterHttpMessageConverter,这里介绍SpringWeb的另一个重要的接口HandlerExceptionResolver。,并通过自定义注解@MyControllerAdvice@MyExceptionHandler,来实现异常的拦截。

    ControllerAdvice和ExceptionHandler

    DispatcherServlet会读取ApplicationContext中是实现了HandlerExceptionResolver接口的bean,并在出现异常时调用对应的方法处理异常,入口是在doDispatch方法中,这里有两次try-catch,最内的异常捕获后交给了processDispatchResult来处理。

        protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
            ...
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;
                try {
                    ...
                }
                catch (Exception ex) {
                    dispatchException = ex;
                }
                processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
            }
            catch (Exception ex) {
                triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
            }
            catch (Error err) {
                triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
            }
            finally {
                ...
            }
        }
    

    HandlerExceptionResolver主要的一个实现类是ExceptionHandlerExceptionResolver,这个类扫描了ControllerAdviceExceptionHandler定义的类和方法来提供异常处理。
    ControllerAdvice是围绕Controller提供嵌入操作,下面的类的注释介绍了它的作用,这里主要用到了ExceptionHandler

    It is typically used to define {@link ExceptionHandler @ExceptionHandler},
    {@link InitBinder @InitBinder}, and {@link ModelAttribute @ModelAttribute}
    methods that apply to all {@link RequestMapping @RequestMapping} methods
    

    ControllerAdviceBean负责加载@ControllerAdvice注释的类,稍后会自定义这个类来实现自定义功能。
    ExceptionHandler是注解在处理特定异常的方法上,由ExceptionHandlerMethodResolver来扫描加载。

    自定义ExceptionHandler

    接下来就是自定义@MyControllerAdvice@MyExceptionHandler来实现异常拦截。完整的代码还是放在Github上了。

    1. 自定义注解,MyControllerAdvice加上@Component,可以被Spring加载扫描到。
        @Target({ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @Component
        public @interface MyControllerAdvice {
            String[] value() default {};
        }
    
        @Target(ElementType.METHOD)
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        public @interface MyExceptionHandler {
            Class<? extends Throwable>[] value() default {};
        }
    
    1. 定义Controller
        @Controller
        public static class MyController {
            @RequestMapping
            @ResponseBody
            public Object index() {
                throw new RuntimeException("hello world");
            }
        }
    
    1. 定义Exception Handler,这里接收到异常后返回一个map。
        @MyControllerAdvice
        public static class MyExceptionHandlerClass {
            @MyExceptionHandler(RuntimeException.class)
            @ResponseBody
            public Map<String, String> handleException(RuntimeException runtimeException) {
                Map<String, String> data = new HashMap<String, String>();
                data.put("msg", runtimeException.getMessage());
                return data;
            }
        }
    
    1. 初始化并配置MyExceptionHandlerExceptionResolverWebMvcConfigurationSupport最终导出的是HandlerExceptionResolverComposite。这个类会集成配置的HandlerExceptionResolver。所以这里继承了WebMvcConfigurationSupport,并在HandlerExceptionResolver列表中加入了自定义的MyExceptionHandlerExceptionResolver
        @Configuration
        public static class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
            public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
                MyExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = new MyExceptionHandlerExceptionResolver();
                exceptionHandlerExceptionResolver.setContentNegotiationManager(mvcContentNegotiationManager());
                exceptionHandlerExceptionResolver.setMessageConverters(getMessageConverters());
                exceptionHandlerExceptionResolver.setApplicationContext(getApplicationContext());
                exceptionHandlerExceptionResolver.afterPropertiesSet();
                exceptionResolvers.add(exceptionHandlerExceptionResolver);
            }
        }
    
    1. 自定义ExceptionHandlerExceptionResolver,继承了ExceptionHandlerExceptionResolver,重用了大部分逻辑,
      同时为了加载自定义的注解,使用了MyControllerAdviceBeanMyExceptionHandlerMethodResolver两个新的类,用来加载自定义的注解,并保存在exceptionHandlerCacheexceptionHandlerAdviceCache中。在使用的时候,在根据Exception的类型来取得对应的方法。
        public static class MyExceptionHandlerExceptionResolver extends ExceptionHandlerExceptionResolver {
            private final Map<Class<?>, MyExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<Class<?>, MyExceptionHandlerMethodResolver>(64);
            private final Map<MyControllerAdviceBean, MyExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<MyControllerAdviceBean, MyExceptionHandlerMethodResolver>();
            @Override
            public void afterPropertiesSet() {
                initExceptionHandlerAdviceCache();
                super.afterPropertiesSet();
            }
            private void initExceptionHandlerAdviceCache() {
                if (getApplicationContext() == null) {
                    return;
                }
                List<MyControllerAdviceBean> adviceBeans = MyControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
                AnnotationAwareOrderComparator.sort(adviceBeans);
                for (MyControllerAdviceBean adviceBean : adviceBeans) {
                    MyExceptionHandlerMethodResolver resolver = new MyExceptionHandlerMethodResolver(adviceBean.getBeanType());
                    if (resolver.hasExceptionMappings()) {
                        this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
                    }
                }
            }
            public ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
                Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
                if (handlerMethod != null) {
                    MyExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
                    if (resolver == null) {
                        resolver = new MyExceptionHandlerMethodResolver(handlerType);
                        this.exceptionHandlerCache.put(handlerType, resolver);
                    }
                    Method method = resolver.resolveMethod(exception);
                    if (method != null) {
                        return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
                    }
                }
                for (Map.Entry<MyControllerAdviceBean, MyExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
                    MyExceptionHandlerMethodResolver resolver = entry.getValue();
                    Method method = resolver.resolveMethod(exception);
                    if (method != null) {
                        return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
                    }
                }
                return null;
            }
        }
    
    1. 自定义ExceptionHandlerMethodResolver,这里重用了ExceptionHandlerMethodResolver的大部分内容。只是替换了注解的类型。
        public static class MyExceptionHandlerMethodResolver {
            public static final ReflectionUtils.MethodFilter EXCEPTION_HANDLER_METHODS = new ReflectionUtils.MethodFilter() {
                public boolean matches(Method method) {
                    return (AnnotationUtils.findAnnotation(method, MyExceptionHandler.class) != null);
                }
            };
            private static final Method NO_METHOD_FOUND = ClassUtils.getMethodIfAvailable(System.class, "currentTimeMillis");
            private final Map<Class<? extends Throwable>, Method> mappedMethods = new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);
            private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);
            public MyExceptionHandlerMethodResolver(Class<?> handlerType) {
                for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
                    for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
                        addExceptionMapping(exceptionType, method);
                    }
                }
            }
            @SuppressWarnings("unchecked")
            private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
                List<Class<? extends Throwable>> result = new ArrayList<Class<? extends Throwable>>();
                detectAnnotationExceptionMappings(method, result);
                if (result.isEmpty()) {
                    for (Class<?> paramType : method.getParameterTypes()) {
                        if (Throwable.class.isAssignableFrom(paramType)) {
                            result.add((Class<? extends Throwable>) paramType);
                        }
                    }
                }
                Assert.notEmpty(result, "No exception types mapped to {" + method + "}");
                return result;
            }
            public void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
                MyExceptionHandler annot = AnnotationUtils.findAnnotation(method, MyExceptionHandler.class);
                result.addAll(Arrays.asList(annot.value()));
            }
            private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
                Method oldMethod = this.mappedMethods.put(exceptionType, method);
                if (oldMethod != null && !oldMethod.equals(method)) {
                    throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
                            exceptionType + "]: {" + oldMethod + ", " + method + "}");
                }
            }
            public boolean hasExceptionMappings() {
                return !this.mappedMethods.isEmpty();
            }
            public Method resolveMethod(Exception exception) {
                return resolveMethodByExceptionType(exception.getClass());
            }
            public Method resolveMethodByExceptionType(Class<? extends Exception> exceptionType) {
                Method method = this.exceptionLookupCache.get(exceptionType);
                if (method == null) {
                    method = getMappedMethod(exceptionType);
                    this.exceptionLookupCache.put(exceptionType, (method != null ? method : NO_METHOD_FOUND));
                }
                return (method != NO_METHOD_FOUND ? method : null);
            }
            private Method getMappedMethod(Class<? extends Exception> exceptionType) {
                List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();
                for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
                    if (mappedException.isAssignableFrom(exceptionType)) {
                        matches.add(mappedException);
                    }
                }
                if (!matches.isEmpty()) {
                    Collections.sort(matches, new ExceptionDepthComparator(exceptionType));
                    return this.mappedMethods.get(matches.get(0));
                } else {
                    return null;
                }
            }
        }
    
    1. 自定义ControllerAdviceBean,这里简化了ControllerAdviceBean的使用属性,本来还有package和类型的过滤。findAnnotatedBeans方法是扫描注解的方法。
        public static class MyControllerAdviceBean implements Ordered {
            private final Object bean;
            private final BeanFactory beanFactory;
            private final int order;
            private MyControllerAdviceBean(Object bean, BeanFactory beanFactory) {
                this.bean = bean;
                this.beanFactory = beanFactory;
                this.order = initOrderFromBean(bean);
            }
            public int getOrder() {
                return this.order;
            }
            public Class<?> getBeanType() {
                Class<?> clazz = (this.bean instanceof String ?
                        this.beanFactory.getType((String) this.bean) : this.bean.getClass());
                return ClassUtils.getUserClass(clazz);
            }
            public Object resolveBean() {
                return (this.bean instanceof String ? this.beanFactory.getBean((String) this.bean) : this.bean);
            }
            @Override
            public boolean equals(Object other) {
                if (this == other) {
                    return true;
                }
                if (!(other instanceof MyControllerAdviceBean)) {
                    return false;
                }
                MyControllerAdviceBean otherAdvice = (MyControllerAdviceBean) other;
                return (this.bean.equals(otherAdvice.bean) && this.beanFactory == otherAdvice.beanFactory);
            }
            @Override
            public int hashCode() {
                return this.bean.hashCode();
            }
            @Override
            public String toString() {
                return this.bean.toString();
            }
            public static List<MyControllerAdviceBean> findAnnotatedBeans(ApplicationContext applicationContext) {
                List<MyControllerAdviceBean> beans = new ArrayList<MyControllerAdviceBean>();
                for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class)) {
                    if (applicationContext.findAnnotationOnBean(name, MyControllerAdvice.class) != null) {
                        beans.add(new MyControllerAdviceBean(name, applicationContext));
                    }
                }
                return beans;
            }
            private static int initOrderFromBean(Object bean) {
                return (bean instanceof Ordered ? ((Ordered) bean).getOrder() : initOrderFromBeanType(bean.getClass()));
            }
            private static int initOrderFromBeanType(Class<?> beanType) {
                return OrderUtils.getOrder(beanType, Ordered.LOWEST_PRECEDENCE);
            }
        }
    
    1. 程序的入口
        public static void main(String[] args) throws ServletException, IOException {
            MockServletContext mockServletContext = new MockServletContext();
            MockServletConfig mockServletConfig = new MockServletConfig(mockServletContext);
            AnnotationConfigWebApplicationContext annotationConfigWebApplicationContext = new AnnotationConfigWebApplicationContext();
            annotationConfigWebApplicationContext.setServletConfig(mockServletConfig);
            annotationConfigWebApplicationContext.register(CustomizeExceptionHandlerTest.class);
            DispatcherServlet dispatcherServlet = new DispatcherServlet(annotationConfigWebApplicationContext);
            dispatcherServlet.init(mockServletConfig);
            MockHttpServletResponse response = new MockHttpServletResponse();
            MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
            request.addHeader("Accept", "application/json");
            request.setContent(("result,hello world;date," + Calendar.getInstance().getTimeInMillis()).getBytes());
            dispatcherServlet.service(request, response);
            System.out.println(new String(response.getContentAsByteArray()));
        }
    

    运行程序就会发现捕获了RuntimeException,返回了对应的map。

    结语

    这里主要介绍了HandlerExceptionResolver的使用和自定义,目前为止已经介绍了HandlerMappingHandlerAdapter
    HandlerExceptionResolver三个主要的SpringWeb的内部实现的接口,已经涵盖了DispatcherServlet的基本功能内部实现。

    相关文章

      网友评论

        本文标题:深入Spring:自定义ExceptionHandler

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