spring @ExceptionHandler 异常处理4

版权声明:欢迎交流、沟通 QQ:983433479 微信:wj983433479 ;努力学习~赚钱养家不是梦。 https://blog.csdn.net/u012881904/article/details/80640601

spring @ExceptionHandler 异常处理4

一、背景

从前面的几篇的博客的介绍,了解了基本的spring异常体系的处理,但是说实话使用非常方便,灵活性更高的还是使用注解的形式进行处理,@ExpectionHandler 当然是我们比较喜欢的一种形式,可以自定义自己喜欢的各种形式的异常的处理,每一种异常的处理都是分开的,不融合在一块!如果是自己写的估计也就是冗余在一块啦!

二、继承图

这里写图片描述
前面的几篇博客已经将HandlerExceptionResolver的其中的几个继承的子类进行了一一的介绍,可以异步去看看,比如AbstractHandlerExceptionResolver、DefaultHandlerExceptionResolver、ResponseEntityExceptionHandler、ResponseEntityExceptionHandler 移步到这里去看看 spring @ExceptionHandler 异常处理3,其中对于注解@ExpectionHandler的实现原理没有进行介绍哦!这篇进行简单分析 AbstractHandlerMethodExceptionResolver 、ExceptionHandlerExceptionResolver!这两个类主要是处理 HandlerMethod类型的执行异常、已经对执行@ExpectionHandler处理过程中,处理策略进行了管理。看源码一个就是了解处理策略,自己在使用的过程中才会胸有成竹,不会懵逼!第二个就是好奇心,这个是怎么实现的,程序员肯定是对这些感兴趣的!

三、AbstractHandlerMethodExceptionResolver

AbstractHandlerMethodExceptionResolver 是AbstractHandlerExceptionResolver的子类,AbstractHandlerExceptionResolver 进行了 Set< ?> mappedHandlers,过滤当前支持的Handler实例的集合,Class< ?>[] mappedHandlerClasses 过滤handler 实例是子类或者子接口才行!这些都是进行啦简单的过滤原则,具体可以看看源码!最好是能够下载spring 源码通过本地能够修改注释,看起来才过瘾!通过调试跟踪很难捕捉到细节的实现,特别是英语捉急的,大段段的英文更加难以适应,spring进行大量的封装抽象!不花功夫很难理解。说了这么多那么 AbstractHandlerMethodExceptionResolver 是干啥的!这里也是进行了一层过滤,主要是为了支持局部的@ExpectionHandler 和全局的@ExpectionHandler的支持,因为这种类型的处理方式的Handler 就是HandlerMethod类型的,这里只过滤支持处理 HandlerMethod类型的处理器。HandlerMethod 就是可以这么理解,对于当前请求这行某个特定的方法的封装,比如 现在使用@RequestMapping 是放在某个特定类上的特定的方法上,那么这个方法就是执行的HandlerMethod 。通过这个类可以通过反射获取到这个class中所有的注解信息哦!有兴趣的可以看看 RequestMappingHandlerAdapter and RequestMappingHandlerMapping
这里写图片描述

说歪啦!继续 AbstractHandlerMethodExceptionResolver ,这个类就是进行过滤,只支持Handler为HandlerMethod
这里写图片描述

四、ExceptionHandlerExceptionResolver

ExceptionHandlerExceptionResolver 才是重点,重点处理 @ExceptionHandler注解哦!这个是重点!
看看实现的spring接口

implements ApplicationContextAware, InitializingBean

spring 官方文档中的
看上面这张spring的官方图片,所以第一步应该看看这个类的初始化周期函数,做了什么?initExceptionHandlerAdviceCache,初始化tExceptionHandlerAdvice 相当于初始化全局的 @ExpectionHandler 如果某个类上面有@ControllerAdvice ,就相当于这个类中的@ExpectionHandler 就是全局的异常解析器啦哦!下面的的这两个HandlerMethodArgumentResolver,and HandlerMethodReturnValueHandler 这两个熟悉的家伙,参数解析器和返回值解析器,应该说非常的常见啦!具体了解慢慢的来,和RequestMappingAdatpter 里面的实现逻辑差不多哦!这里这些不关系哈哈!

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

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

1、找到当前空间中所有存在的@ControlAdvice所有的Bean
2、根据全局的@ControllAdvice 进行排序哦
3、根据特定的ControllerAdviceBean中解析出来有处理异常的@ExpectionHandle方法,ExceptionHandlerMethodResolver
4、 private final Map< ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
new LinkedHashMap< ControllerAdviceBean, ExceptionHandlerMethodResolver>(); 特定的对应特定的异常方法解析器缓存下来。
5、 看看当前是否注册了全局的ResponseBodyAdvice,这个不是关注的重点,不过了解挺有用的,处理加解密 谈谈springmvc的ResponseBodyAdvice

private void initExceptionHandlerAdviceCache() {
      /**
        * 找到当前空间中所有存在的@ControlAdvice所有的Bean
        */
       List<ControllerAdviceBean> adviceBeans = 
       ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
       //根据order的大小,确定处理的顺序哦!
       AnnotationAwareOrderComparator.sort(adviceBeans);
       for (ControllerAdviceBean adviceBean : adviceBeans) {
           //将@ControlAdvice的Bean中查找异常的处理程序
           //可以通过resolver去判断当前是否可以支持处理特定的异常
           ExceptionHandlerMethodResolver resolver = 
           new ExceptionHandlerMethodResolver(adviceBean.getBeanType());
           //有异常,一个异常或者多个异常对应一个方法哦!然后将对应bean处理对应的异常处理保存下来哦!
           if (resolver.hasExceptionMappings()) {
               this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
           }
           /**
            *[谈谈springmvc的ResponseBodyAdvice](https://my.oschina.net/emperror/blog/782284)
            */
           if (ResponseBodyAdvice.class.isAssignableFrom(adviceBean.getBeanType())) {
               this.responseBodyAdvice.add(adviceBean);
           }
       }
    }

找到当前空间中所有存在的@ControlAdvice所有的Bean

List< ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
1、查找到全局的所有的注册的Bean的名称 BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class)
2、查找bean名称 是否存在特定的注解 applicationContext.findAnnotationOnBean(name, ControllerAdvice.class)
3、创建实例
这里写图片描述

ExceptionHandlerMethodResolver 解析ControllerAdviceBean 中方法的@ExpectionHandler注解,获取到当前能够处理的全部异常信息

1、查找当前Bean中的所有的方法,且方法中含有注解 @ExpectionHandler
2、将含有异常处理的方法,全部放置到缓存中mappedMethods 中保存起来,一个异常只能对应一个处理方法,否则会报错。

    /**
     * 一个异常对应的一个Method,切不能出现重复
     */
    private final Map<Class<? extends Throwable>, Method> mappedMethods =
            new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);

    /**
     * 处理过程中缓存!不用每一个出现异常都进行调用,没有找到也进行处理,
     * 缓存为当前方法不能处理 NO_METHOD_FOUND 
     */
    private final Map<Class<? extends Throwable>, Method> exceptionLookupCache =
            new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);

        /**
     * Arbitrary {@link Method} reference, indicating no method found in the cache.
     */
    private static final Method NO_METHOD_FOUND = ClassUtils.getMethodIfAvailable(
    System.class, "currentTimeMillis");
查找当前Bean中的所有的方法,且方法中含有注解 @ExpectionHandler

这种工具类方法,spring提供了简单的工具给我们使用

   /**
     * 查找当前类或者父类、接口中的带有注解@ExceptionHandler 的方法
     * A filter for selecting {@code @ExceptionHandler} methods.
     */
    public static final MethodFilter EXCEPTION_HANDLER_METHODS = new MethodFilter() {
        @Override
        public boolean matches(Method method) {
            return (AnnotationUtils.findAnnotation(method, ExceptionHandler.class) != null);
        }
    };
    //找到所有的含有注解的方法
   MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)
找到当前方法上的注解的值,并缓存起来(如果没有,根据参数值就行添加)

从注解上发现异常的值 Spring Boot @ControllerAdvice 处理全局异常,返回固定格式Json

   /**
     * 在方法上找到注解哦
     * @param method
     * @param result
     */
    protected void detectAnnotationExceptionMappings(Method method, 
    List<Class<? extends Throwable>> result) {
        ExceptionHandler ann = AnnotationUtils.findAnnotation(method, 
        ExceptionHandler.class);
        result.addAll(Arrays.asList(ann.value()));
    }

如果注解值上面没有异常呢?spring的做法是遍历参数,将所有的参数只要是异常体系中的都可以处理的,添加到缓存中

    @ExceptionHandler
    @RespondBody
    public object nullPointerExceptionHandler(NullPointerException ex) {  
        return .......;  
    }  

这里写图片描述

将异常Class对应的异常处理方法添加到缓存中,因为一个异常只能对应一个处理方法,这里回进行唯一性校验,存在重复的就失败啦!

    /**
     * 不明确的@ExceptionHandler方法映射为 是不允许的
     * @param exceptionType
     * @param method
     */
    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 + "}");
        }
    }

所有的整和在一起就是处理查找@ExpectionHandler异常啦


    /**
     * A constructor that finds {@link ExceptionHandler} methods in the given type.
     * @param handlerType the type to introspect
     */
    public ExceptionHandlerMethodResolver(Class<?> handlerType) {
        //找到所有的ExceptionHandler 的方法,
        for (Method method : MethodIntrospector. 
        selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
            //找到方法中@ExceptionHandler中的配置的异常类的信息,可能有多个,一一遍历;
            for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
                //添加到缓存中 mappedMethods中保存起来方便处理数据的时候进行调用。
                addExceptionMapping(exceptionType, method);
            }
        }
    }
这里有个问题需要思考?

如果来了异常,如何通过判断当前的异常是否可以处理呢?肯定是通过遍历所有的mappedMethods ,如果当前的异常是这个Class的子类 xxxObjec.isAssignableFrom(xxxClass),这个是个思路,但是RunTimeExpection、BussinExpection 继承RunTime异常,如果出现了一个BussinExpection 或者 RunTimeExpection,那么到底应该处理哪个?Java只支持单继承,那么通过比较所有查找到符合条件的Class,然后进行继承深度的排序,找到最合适的那个
这里写图片描述
排序比较哦!
这里写图片描述
完整的查找异常的方法
这里写图片描述

通过从 ControllerAdviceBean中获取到的异常方法的缓存

ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());
查看当前的ExceptionHandlerMethodResolver 中是否存在异常类对应的异常方法,存在就注册到全局的异常注册器中

/**
     * 一个异常对应的一个Method,切不能出现重复
     */
    private final Map<Class<? extends Throwable>, Method> mappedMethods =
            new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);

    /**
     * Whether the contained type has any exception mappings.
     */
    public boolean hasExceptionMappings() {
        return !this.mappedMethods.isEmpty();
    }

缓存下来,当真正有异常信息的时候在进行处理,最后还是有一个ResponseBodyAdvice,看看这个博客谈谈springmvc的ResponseBodyAdvice,到现在为止,全局的异常参数的解析工作已经完成!就等着发生异常后的数据的处理。
private final Map< ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
new LinkedHashMap< ControllerAdviceBean, ExceptionHandlerMethodResolver>();

for (ControllerAdviceBean adviceBean : adviceBeans) {
            //将@ControlAdvice的Bean中查找异常的处理程序
            //可以通过resolver去判断当前是否可以支持处理特定的异常
            ExceptionHandlerMethodResolver resolver = new 
            ExceptionHandlerMethodResolver(adviceBean.getBeanType());
            //有异常,一个异常或者多个异常对应一个方法哦!然后将对应bean处理对应的异常处理保存下来哦!
            if (resolver.hasExceptionMappings()) {
                this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
            }
            /**
             *[谈谈springmvc的ResponseBodyAdvice](https://my.oschina.net/emperror/blog/782284)
             */
            if (ResponseBodyAdvice.class.isAssignableFrom(adviceBean.getBeanType())) {
                this.responseBodyAdvice.add(adviceBean);
            }
        }

五、出现异常,处理过程

前面几篇博客已经说过啦!如何解析,这里直接定位到 doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) !这个异常为啥会定位到这里,是根据HandlerExpectionResolver的顺序进行排序后根据执行的顺序一个个排队进行处理,如果一个不行下一个继续哦,只要是返回Null,下一个异常解析器继续处理,HanderMethod就是我们调用具体的方法过程中出现异常的那个类哦!
1、找到能够处理当前异常的那个方法,然后通过反射执行那个方法,获取到返回值,返回值的处理和RequestMappingHandlerAdapter 中处理参数映射、处理返回值一样的!

1、找到异常处理中的某个方法

如下是缓存特定的异常对应的ExceptionHandlerMethodResolver解析器,一个类对应一个解析器

/**
     * 异常类对应的异常方法的处理解决参数哦!
     */
    private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =
            new ConcurrentHashMap<Class<?>, ExceptionHandlerMethodResolver>(64);
  • 查看当前类中是否已经在缓存中存在有啦,异常解决方案!
  • 如果没有,搜索当前方法中存在的@ExpectionHandler的方法与ControlAdvice的处理方式类似
  • 查看当前本地的扫描是否能够处理当前的这种异常情况的处理哦!
  • 如果还是没有解决,这个时候就是查找全局中@ControllerAdvice中的处理能不能找到哦!

根据上面的策略,我们就可以知道如何处理啦!resolveMethod就是上面查找异常方法是不是当前的ExceptionHandlerMethodResolver支持的类型哦!先本地然后在全局!查找全局的时候这里还有一个ControllerAdvice的选择器,类似过滤的意思!全局全局!全局也是可以过滤的哦!
这里写图片描述

2、返回 ServletInvocableHandlerMethod?

返回ServletInvocableHandlerMethod 是什么鬼?其实这个就是对于方法的封装,方便处理反射调用!对于方法的反射调用进行了深度的封装,对于参数解析,参数值设置、返回值处理等等,进行很多的处理,先看看继承图
这里写图片描述

3、完成的执行一次

看到了?执行反射调用,这些都是InvocableHandlerMethod封装的结果,对于参数处理、返回值处理等等做了非常多的工作,他们结合HandlerMethodArgumentResolver、HandlerMethodReturnValueHandler做了非常多的工作、比如@RepondBody 处理,所有的一切都搞定啦!要理解其实这里就是反射处理!处理异常方法参数映射、异常方法的返回执行处理 ,还记得?第三篇博客中的ResponseEntityExceptionHandler 的处理???这个就是 参数解析的功力。

这里写图片描述
这里写图片描述

六、总结

代码不多,要完成说清楚真的不容易!涉及的还特别的多!有时间继续聊聊InvocableHandlerMethod,坐在电脑前面4 5个小时的结果!okok!

猜你喜欢

转载自blog.csdn.net/u012881904/article/details/80640601