Springboot全局异常之原理

141 阅读3分钟

如何实现全局异常捕获

@Slf4j
@RestControllerAdvice
@Order(2)
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    public ApiResponse<Void> handleException(Exception e) {
        log.error("global exception happened!", e);
        return ApiResponse.ofFailure(SYSTEM_ERROR, "应用异常");
    }
}

在异常类上加@RestControllerAdvice注解并在对应方法加@ExceptionHandler注解,在方法体内写上自身服务的处理逻辑即可实现全局自定义异常处理。 可以通过指定@ExceptionHandler的value字段值来写不同的方法实现不同的异常捕捉逻辑。这里就不一一展示了。

到这里,怎么用我们已经学会了。那么,为什么可以做到呢。好奇的小伙伴跟我一起继续探索下。知识的海洋永无止境哦。

Springboot是怎么做到全局异常处理的

  • 处理的入口在DispatcherServlet的doDispatch方法

Snipaste_2023-10-24_14-35-32.png 图中标红框的方法主要用于接口返回结果的处理,无论有没有异常都会在此方法里进行处理。

这里讲句题外话,doDispatch这个方法包含了面试经典八股文,SpringMVC的流程,dispatchServlet -> handlerAdapter -> handlerMapping -> ViewAndResolver。熟不熟悉,惊不惊讶。其实都是在这个方法里实现的。

Snipaste_2023-10-24_14-42-15.png

  • 进入到处理结果的方法,当发生异常时,exception这个形参就会有值,然后通过判断进入到图中框框的地方,进入processHandlerException方法。这么简单的代码在座的各位想必都能看得懂吧。

不会吧 不会吧 有人不懂的话欢迎评论区沟通哦 JYM

Snipaste_2023-10-24_14-48-14.png

  • 到这里,快接近真实调用我们的全局异常处理类的地方了。此处这个resolver是HandlerExceptionResolverComposite。感兴趣的可以自行查看。当调用完该类的resolveException方法后,由于exMv不为null,此处会直接break出循环。

Snipaste_2023-10-24_14-53-49.png

  • 然后此处HandlerExceptionResolverComposite的resolves也有多个,糊糊涂涂的,随便选一个吧。其实是ExceptionHandlerExceptionResolver哦。其实此类并没有resolveException方法。但是,注意但是,他的父类的父类有,就等于他有。

Snipaste_2023-10-24_15-00-54.png

  • doResolveHandlerMethodException这个方法做了两件大事。一是通过getExceptionHnadlerMethod解析出了异常方法对象。二是通过异常方法对象去调用真实的处理方法,也就是我们真实的异常处理类。

那么getExceptionHnadlerMethod是如何解析出异常类的呢?是上述提到的ExceptionHandlerExceptionResolver类中有一个map exceptionHandlerAdviceCache属性,其中key是我们异常类的bean信息,value是异常类所有的自定义异常方法。

Snipaste_2023-10-24_15-15-11.png

  • 上图我们讲到的解析出来的异常方法对象就是ServletInvocableHandlerMethod,此时已经把我们定义的异常类bean引用和需要调用的异常方法解析出来了。把捕获到的异常作为参数传进去。只需要通过反射调用方法取到返回值就可以返回给前端了;

到这里,我们已经把Springboot如何将异常解析成我们的自定义异常讲清楚了。Tips:自定义异常不能在Controller层catch异常,要抛出来。

那么Springboot是如何识别到异常处理类并解析的呢

其实这个问题可以换成Springboot如何解析我们上文提到的两个关于自定义异常的注解

Snipaste_2023-10-24_16-51-55.png

  1. 答案就在ExceptionHandlerExceptionResolver类里,此类继承了初始化InitializingBean,在create此类的bean即createBean创建完对象后会调用这个方法。

Snipaste_2023-10-24_16-52-13.png 2. 重点在initExceptionHandlerAdviceCache方法中,findAnnotatedBeans方法会找到applicationContext中所有标注了ControllerAdvice的bean。后续ptionHandlerAdviceCache字段中就可以在发生异常时调用这个bean的异常处理方法。具体逻辑可以在上文中找到。

Snipaste_2023-10-24_16-53-13.png 3. 好了,最后了。ExceptionHandlerMethodResolver这个类,会通过步骤2找到的bean信息,解析bean中包含了ExceptionHandler注解的方法。 4. 解析完这两个标签,这个原理我们就掌握了。

结语

此篇文章告诉大家Springboot的前世今生,如何使用以及原理是什么。各位看官多支持下,点赞收藏作者才有动力继续下面的创作。有错误欢迎指正哦。