@ControllerAdvice 异常处理原理分析

1,006 阅读5分钟

前言

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

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

@ControllerAdvice 概述

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

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

使用demo

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

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

/**
* 
**/
@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级别的异常
@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接口

    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 实现

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

    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); 具体实现如下

	// 异常处理方法解析器 构造方法
	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 接口,负责将异常传递给合适的异常处理方法。

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 的异常处理方法 (重点落在这一步)
/**
* @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); 获取异常的处理方法,核心处理逻辑如下:

	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;
		}
	}

反射调用异常处理类

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 初始化阶段),再到异常的捕捉,以及如何找到对应的异常处理方法,最后反射调用,返回结果。希望这篇文章对你有所帮助,欢迎各位老铁点赞收藏!!!