前言
“@全体成员 数据库的一切异常都不能直接暴露给前端” 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()); } }
- 定义
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
注解的类中解析异常处理方法。
实现过程
实现过程主要分为两流程,异常处器的加载初始化。然后就是异常的捕捉,以及调用对应的处理器;
加载流程如下图:
异常捕获流程:
初始化异常解析器
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
这个就是找到对应异常的处理类已经处理方法
主要分为三步
- 查询Controller 本身是否有异常处理方法
- 处理代理类情况
- 查找全局 @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 初始化阶段),再到异常的捕捉,以及如何找到对应的异常处理方法,最后反射调用,返回结果。希望这篇文章对你有所帮助,欢迎各位老铁点赞收藏!!!