@ControllerAdvice和@ExceptionHandler的原理

23 篇文章 0 订阅
6 篇文章 0 订阅


前言

现在大家都习惯使用Spring框架了,而且平时的业务系统中的异常捕捉及处理都是靠@ControllerAdvice和@ExceptionHanlder两个注解配合使用的。

一、简单使用

1. jar的引入

其实我不写,哈哈哈哈。
看了下这两个注解都是spring-web里的,不过因为现在一般项目都是使用springboot启动的,所以不用自己特意去引入spring-web。

2. 废话不多说,上代码

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BizException.class)
    public ApiResponseEntity bizException(BizException exception) {
        return ApiResponseEntity.ofError(exception);
    }

    @ExceptionHandler(Exception.class)
    public ApiResponseEntity exception(Exception exception) {
        BizException bizException = new BizException(exception.getMessage());
        return ApiResponseEntity.ofError(bizException);
    }

}

@RestControllerAdvice注解是@ControllerAdvice注解的子类,不用多说了吧。这样使用,可以基于不同的异常类型进行不同的处理。BizException是我们自定义的异常,这个异常继承了RuntimeException,是为了进行业务上的回滚用的。

二、查看原理

1.抽丝剥茧

此处我就不按照思考的顺序去阐明了,按照Springboot的启动顺序来说明。

1.1 WebMvcAutoConfiguration

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
....
....
/**
	 * Configuration equivalent to {@code @EnableWebMvc}.
	 */
	@Configuration
	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {

 }
....
....
}

相信大家对于添加了@Configuration注解的启动过程应该很熟悉了,Spring会对添加了@Configuration注解的类进行Gglib代理,并进行@Bean注解的类解析。

1.2 EnableWebMvcConfiguration

EnableWebMvcConfiguration类是WebMvcAutoConfiguration类的内部类,但是也加了@Configuration注解,所以同样会进行类中的@Bean注解的类的实例化。下面是EnableWebMvcConfiguration的类继承图
在这里插入图片描述

1.3 WebMVCConfigurationSupport

从上面的类继承图可以看出EnableWebMvcConfiguration是WebMVCConfigurationSupport的子类,所以在Spring处理EnableWebMvcConfiguration中加@Bean注解的方法时,就会把WebMVCConfigurationSupport中的加了@Bean的方法一起处理。

1.4 HandlerExceptionResolver

	@Bean
	public HandlerExceptionResolver handlerExceptionResolver() {
		List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
		configureHandlerExceptionResolvers(exceptionResolvers);
		if (exceptionResolvers.isEmpty()) {
		    // debug进入这一句,这一句会处理我们自定义的GlobalExceptionHandler
			addDefaultHandlerExceptionResolvers(exceptionResolvers);
		}
		// 默认情况下,这句没有作用
		extendHandlerExceptionResolvers(exceptionResolvers);
		HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
		composite.setOrder(0);
		composite.setExceptionResolvers(exceptionResolvers);
		return composite;
	}

在WebMVCConfigurationSupport的上述BeanMethod中往Spring中注入HandlerExceptionResolver。

protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
		ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
		exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager());
		exceptionHandlerResolver.setMessageConverters(getMessageConverters());
		exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
		exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
		if (jackson2Present) {
			exceptionHandlerResolver.setResponseBodyAdvice(
					Collections.singletonList(new JsonViewResponseBodyAdvice()));
		}
		if (this.applicationContext != null) {
			exceptionHandlerResolver.setApplicationContext(this.applicationContext);
		}
		// 此处调用了ExceptionHandlerExceptionResolver的afterPropertiesSet(),下面文章解读了afterPropertiesSet的作用
		exceptionHandlerResolver.afterPropertiesSet();
		// 把ExceptionHandlerExceptionResolver加入到了exceptionResolvers中
		exceptionResolvers.add(exceptionHandlerResolver);
		// 放入一个ResponseStatusExceptionResolver
		ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
		responseStatusResolver.setMessageSource(this.applicationContext);
		exceptionResolvers.add(responseStatusResolver);
		// 放入DefaultHandlerExceptionResolver
		exceptionResolvers.add(new DefaultHandlerExceptionResolver());
	}

1.5 ExceptionHandlerExceptionResolver

	@Override
	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBodyAdvice beans
		// 注意看这句注释
		initExceptionHandlerAdviceCache();

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

可以看到上来就调用initExceptionHandlerAdviceCache方法

	private void initExceptionHandlerAdviceCache() {
		if (getApplicationContext() == null) {
			return;
		}

		// 找出所有加了@ControllerAdvice注解的类
		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
		AnnotationAwareOrderComparator.sort(adviceBeans);

		for (ControllerAdviceBean adviceBean : adviceBeans) {
			Class<?> beanType = adviceBean.getBeanType();
			if (beanType == null) {
				throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
			}
			// 此处将GlobalExceptionHandler封装成ExceptionHandlerMethodResolver,并把类里面加了@ExceptionHandler注解的方法扫描处理,设置为内部属性,构造方法见下面1.6的代码片段
			ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
			if (resolver.hasExceptionMappings()) {
			// 将ExceptionHandlerMethodResolver放入了ExceptionHandlerExceptionResolver的内部属性exceptionHandlerAdviceCache中
				this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
			}
			if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
				this.responseBodyAdvice.add(adviceBean);
			}
		}

		// log stuff
	}

下面放上我的截图:
在这里插入图片描述
找出了我们自定义的加了@ControllerAdvice的类GlobalExceptionHandler

1.6 ExceptionHandlerMethodResolver

/**
* A filter for selecting {@code @ExceptionHandler} methods.
 */
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
		AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
		for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
			for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
				addExceptionMapping(exceptionType, method);
			}
		}
	}

2. DispatcherServlet

以前用过SpringMVC编程的同学对于这个Servlet应该不会陌生,web.xml里面配置的Servlet就是这个Servlet。Springboot在启动的过程中也需要对这个类进行实例化。且调用其OnRefresh方法:

	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	/**
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 * 看这个方法名,也知道作用了,初始化策略
	 */
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

其中调用了initHandlerExceptionResolvers方法:

	private void initHandlerExceptionResolvers(ApplicationContext context) {
		this.handlerExceptionResolvers = null;

		if (this.detectAllHandlerExceptionResolvers) {
			// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
			// 此处把Spring容器中的HandlerExceptionResolver类型对象都找出来,其中就有上述的通过@Bean方法生成的HandlerExceptionResolverComposite对象
			Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
					.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
			if (!matchingBeans.isEmpty()) {
				// 将HandlerExceptionResolverComposite对象又放入到了DispatcherServlet的handlerExceptionResolvers中
				this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
				// We keep HandlerExceptionResolvers in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
			}
		}
		else {
			// 此处不讨论,忽略
		}
		// 此处不讨论,忽略
		
	}

通过以上步骤,咱们自定义GlobalExceptionHandler成为了ExceptionHandlerExceptionResolver属性,而ExceptionHandlerExceptionResolver成为了HandlerExceptionResolverComposite的属性,HandlerExceptionResolverComposite又成为了DispatcherServlet的属性。

DispatcherServlet:handlerExceptionResolvers -> 
HandlerExceptionResolverComposite: resolvers ->
ExceptionHandlerExceptionResolver:exceptionHandlerCache ->
ExceptionHandlerMethodResolver=>(GlobalExceptionHandler(咱们的自定义全局异常类))

然后Springboot项目启动成功。

3. How to work to catch a exception

响应熟悉Servlet运行机制的同学,都知道默认情况下接口会调用Servlet中的service()方法,此处因为调用链路较长,进行简单流程分析

DispatcherServlet#service()->
DispatcherServlet#doService()->
DispatcherServlet#doDispatch()-> 该方法会进行try catch操作,并捕获异常,
DispatcherServlet#processDispatchResult()->
DispatcherServlet#processHandlerException()->
HandlerExceptionResolverComposite#resolveException()->
ExceptionHandlerExceptionResolver#doResolveHandlerMethodException

3.1 DispatcherServlet#processHandlerException

看到这个方法里的这段代码,相信大家也就明白是怎么回事了

	if (this.handlerExceptionResolvers != null) {
			// 遍历获得HandlerExceptionResolverComposite
			for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
				exMv = resolver.resolveException(request, response, handler, ex);
				if (exMv != null) {
					break;
				}
			}
		}

3.2 HandlerExceptionResolverComposite#resolveException

public ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

		if (this.resolvers != null) {
		// 此处遍历获得我们的ExceptionHandlerExceptionResolver对象
			for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
				ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
				if (mav != null) {
					return mav;
				}
			}
		}
		return null;
	}

3.3 ExceptionHandlerExceptionResolver#doResolveHandlerMethodException

// 获取到了咱们在GlobalExceptionHandler里定义的异常处理方法
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
.....
.....
// 调用反射执行咱们的异常处理方法
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);

总结

总结一下:

  1. Spring容器初始化的时候先把HandlerExceptionResolver对象放入容器中,间接能链接到我们自定义的GlobalExceptionHandler。
  2. Springboot最后实例化DispatcherServlet的时候,把HandlerExceptionResolver绑定到DispatcherServlet对象上
DispatcherServlet:handlerExceptionResolvers -> 
HandlerExceptionResolverComposite: resolvers ->
ExceptionHandlerExceptionResolver:exceptionHandlerCache ->
ExceptionHandlerMethodResolver=>(GlobalExceptionHandler(咱们的自定义全局异常类))
  1. 方法调用过程中,try-》catch过程中捕捉的异常会通过DispatcherServlet的processHandlerException
DispatcherServlet#service()->
DispatcherServlet#doService()->
DispatcherServlet#doDispatch()-> 该方法会进行try catch操作,并捕获异常,
DispatcherServlet#processDispatchResult()->
DispatcherServlet#processHandlerException()->
HandlerExceptionResolverComposite#resolveException()->
ExceptionHandlerExceptionResolver#doResolveHandlerMethodException()
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值