@ControllerAdvice 异常处理原理分析

前言

“@全体成员 数据库的一切异常都不能直接暴露给前端” springboot的全局异常处理 可通过 @ControllerAdivce/@RestControllerAdvice 来实现。使用之前先看其原理。

今天就从从源码的角度分析 @ControllerAdvice 的实现全局异常捕捉的过程,帮助开发者更深入地理解其工作机制。

@ControllerAdvice 概述

@ControllerAdvice 是一个注解,用于定义全局的控制器建议。它可以用于处理所有控制器抛出的异常、进行全局数据绑定以及提供全局模型属性。

实际开发中基本就是用来全局的异常处理。现在一般用@RestControllerAdvice实现上基本一样,只是返回结果都以json数据格式返回,符合前后端开发规范。

使用demo

实际开发中用得比较多的就是捕获全局异常,当然Controller 层也可以定义自己的异常。Controller级别的异常处理优先级高于全局异常处理。

1.定义全局异常异常处理


java

代码解读

复制代码

/** * **/ @RestControllerAdvice @Slf4j public class GlobalExceptionHandler { /** * 处理自定义异常 */ @ExceptionHandler(BizDataException.class) public ResponseVO<?> handleBizDataException(BizDataException e){ log.error(e.getMessage(), e); return ResponseVO.error(e.getMessage()); } }

  1. 定义Controller级别的异常

java

代码解读

复制代码

@RestController @RequestMapping("/admin/api") public class CommController { @GetMapping(value = "/query") public ResponseVO<?> query(@RequestParam(required = false) String name) { return ResponseVO.ok(service.query(name)); } /** * 处理自定义异常 */ @ExceptionHandler(BizDataException.class) public ResponseVO<?> handleBizDataException(BizDataException e){ log.error(e.getMessage(), e); return ResponseVO.error(e.getMessage()); } }

核心类与注解

在 Spring 的实现中,几个关键类和注解共同构成了 @ControllerAdvice 的功能:

  • @ControllerAdvice: 注解本身,标记类为全局控制器建议。@ExceptionHandler 标记处理异常方法
  • ExceptionHandlerExceptionResolver 遍历@ControllerAdvice 类 初始化异常处理器ExceptionHandlerMethodResolver
  • ExceptionHandlerMethodResolver: 负责从 @ControllerAdvice 注解的类中解析异常处理方法。

实现过程

实现过程主要分为两流程,异常处器的加载初始化。然后就是异常的捕捉,以及调用对应的处理器;

加载流程如下图: 

1733978460960.jpg

 异常捕获流程:

image.png

初始化异常解析器

1.当 Spring 容器启动时,它会扫描所有带有 @ControllerAdvice 注解的类。具体而言,AnnotationConfigServletWebServerApplicationContext 会通过 ClassPathBeanDefinitionScanner 扫描类路径,以发现这些注解。

2.遍历所有@ControllerAdvice标记的Bean,对每个Bean封装成ExceptionHandlerMethodResolver 放入exceptionHandlerAdviceCache Map 中。

exceptionHandlerAdviceCache 这个map非常重要,后面处理异常就会遍历该map 找到对应异常的处理方法

初始化 ExceptionHandlerMethodResolver的时候,就会去解析@ControllerAdvice标记的Bean里面的 异常处理方法封装,放到mappedMethods Map 中。

第2步的处理入口就是ExceptionHandlerExceptionResolver类,这个类实现了InitializingBean#afterPropertiesSet接口


java

代码解读

复制代码

public void afterPropertiesSet() { // 将advice 标记类初始化成 异常处理器 this.initExceptionHandlerAdviceCache(); List handlers; if (this.argumentResolvers == null) { handlers = this.getDefaultArgumentResolvers(); this.argumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers); } if (this.returnValueHandlers == null) { handlers = this.getDefaultReturnValueHandlers(); this.returnValueHandlers = (new HandlerMethodReturnValueHandlerComposite()).addHandlers(handlers); } }

initExceptionHandlerAdviceCache 实现

关键的代码都进行了注释,如下:


java

代码解读

复制代码

private void initExceptionHandlerAdviceCache() { if (this.getApplicationContext() != null) { // 通过上下文,找到ControllerAdviceBean List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(this.getApplicationContext()); Iterator var2 = adviceBeans.iterator(); while(var2.hasNext()) { ControllerAdviceBean adviceBean = (ControllerAdviceBean)var2.next(); Class<?> beanType = adviceBean.getBeanType(); if (beanType == null) { throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean); } // 将一全局 处理异常的bean 封装成 resovler;并且会把bean 中 方法 和 方法对应的异常进行映射。 ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); if (resolver.hasExceptionMappings()) { // 将 adviceBean 和对应的解析器 放到map中 this.exceptionHandlerAdviceCache.put(adviceBean, resolver); } if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) { // 放进list中 this.responseBodyAdvice.add(adviceBean); } } ...................... } }

上面还有一个比较关键的代码 ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); 具体实现如下


java

代码解读

复制代码

// 异常处理方法解析器 构造方法 public ExceptionHandlerMethodResolver(Class<?> handlerType) { // 遍历 ControllerAdvice 类的异常处理 方法 for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) { for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) { addExceptionMapping(exceptionType, method); } } } private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) { // 将异常类型 和 对应处理异常的方法 放到HashMap 中 Method oldMethod = this.mappedMethods.put(exceptionType, method); if (oldMethod != null && !oldMethod.equals(method)) { throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" + exceptionType + "]: {" + oldMethod + ", " + method + "}"); } }

以上就是解析@ControllerAdvice 类,并初始化解析器 ExceptionHandlerMethodResolver的过程。

异常捕捉,反射调用

当控制器抛出异常时,DispatcherServlet 会捕捉到这些异常。在 Spring MVC 中,DispatcherServlet 实现了 HandlerExceptionResolver 接口,负责将异常传递给合适的异常处理方法。


java

代码解读

复制代码

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { ........... try { ........ try { ...................... // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); ....................... } catch (Exception ex) { // 捕捉异常 dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } // 处理返回结果 ,有异常就会处理异常,重新封装返回结果 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { .................. } }

中间步骤就省略了,看看堆栈吧截图吧,从上往下执行: 

在这里插入图片描述

 下面就主要分析如何找到对应的异常处理器

选择异常处理器

上图我们debug 到 ExceptionHandlerExceptionResolver#getExceptionHandlerMethod 这个就是找到对应异常的处理类已经处理方法

主要分为三步

  1. 查询Controller 本身是否有异常处理方法
  2. 处理代理类情况
  3. 查找全局 @ControllerAdvice 的异常处理方法 (重点落在这一步)

java

代码解读

复制代码

/** * @param handlerMethod 抛出异常的 Controller 对应的method * @param exception 捕捉到的异常 **/ protected ServletInvocableHandlerMethod getExceptionHandlerMethod( @Nullable HandlerMethod handlerMethod, Exception exception) { Class<?> handlerType = null; if (handlerMethod != null) { //1. 查询Controller 本身是否有异常处理方法 // Local exception handler methods on the controller class itself. // To be invoked through the proxy, even in case of an interface-based proxy. handlerType = handlerMethod.getBeanType(); 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, this.applicationContext); } //2. 处理代理类情况 // For advice applicability check below (involving base packages, assignable types // and annotation presence), use target class instead of interface-based proxy. if (Proxy.isProxyClass(handlerType)) { handlerType = AopUtils.getTargetClass(handlerMethod.getBean()); } } //3. 查找全局 @ControllerAdvice 的异常处理方法 (重点落在这一步) for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) { ControllerAdviceBean advice = entry.getKey(); if (advice.isApplicableToBeanType(handlerType)) { // 获取到我们自定义的 @ControllerAdvice 标记类的处理器(初始化阶段完成的) ExceptionHandlerMethodResolver resolver = entry.getValue(); // 获取到 解析异常的处理类 Method method = resolver.resolveMethod(exception); if (method != null) { return new ServletInvocableHandlerMethod(advice.resolveBean(), method, this.applicationContext); } } } return null; }

// 获取到 解析异常的处理类 Method method = resolver.resolveMethod(exception); 获取异常的处理方法,核心处理逻辑如下:


java

代码解读

复制代码

private Method getMappedMethod(Class<? extends Throwable> exceptionType) { List<Class<? extends Throwable>> matches = new ArrayList<>(); // 从mapperdMethods Map 中获取对应的异常处理类,mapperdMethods 里面数据就是在初始化阶段完成的 for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) { if (mappedException.isAssignableFrom(exceptionType)) { matches.add(mappedException); } } if (!matches.isEmpty()) { if (matches.size() > 1) { matches.sort(new ExceptionDepthComparator(exceptionType)); } return this.mappedMethods.get(matches.get(0)); } else { return NO_MATCHING_EXCEPTION_HANDLER_METHOD; } }

反射调用异常处理类

java

代码解读

复制代码

protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) { // 获取到 处理异常对应的 Method,封装到ServletInvocableHandlerMethod ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); ................. try { ............. //反射调用 exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments); } //返回视图............. .......... } }

5. 总结

本篇文章分析了异常解析器的初始化过程(bean 初始化阶段),再到异常的捕捉,以及如何找到对应的异常处理方法,最后反射调用,返回结果。希望这篇文章对你有所帮助,欢迎各位老铁点赞收藏!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值