基于@ControllerAdvice注解实现全局异常处理用法和原理的探究

一、@ControllerAdvice注解

1.1、简介

  @ControllerAdvice注解主要用来实现一些全局性的功能,最常见的就是结合@ExceptionHandler注解实现全局异常的处理,这个也是我们这篇内容学习的重点。除了全局异常处理,还可以配合@InitBinder和@ModelAttribute两个注解使用,其中,@InitBinder注解主要用于请求中注册自定义参数的解析,从而达到自定义请求参数格式化的目的;@ModelAttribute注解主要用于表示被注解的方法会在执行目标Controller方法之前执行。

1.2、全局异常处理 用法

  配合@ExceptionHandler注解实现全局的异常处理是@ControllerAdvice注解最常用的功能,我们这一节主要分析两个注解是如何配合实现全局异常处理的,首先我们它们是如何配合使用的:

/**
 * 全局统一的异常处理方案。
 * @author hsh
 *
 */
@ControllerAdvice
public class GlobalExceptionHandler {

	 /**
     * 处理SysRunException异常
     * @return
     */
    @ExceptionHandler(SysBaseException.class)
    @ResponseBody
    Map<String,Object> handleException(SysBaseException e){
    	Map<String, Object> result = new HashMap<String, Object>();
        result.put(QriverSysConstant.SUCCESS, QriverSysConstant.FLAG_FALSE);
        result.put(QriverSysConstant.MSG, e.getErrorMsg());
        return result;
    }

    /**
     * 404 页面不存在拦截器
     * @param request
     * @param response
     * @param e
     * @return
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public ModelAndView handle404Exception(HttpServletRequest request, HttpServletResponse response, NoHandlerFoundException e){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg",e.getMessage());
        mv.addObject("url",request.getRequestURL());
        mv.setViewName("error/400");
        return mv;
    }
	//……
}

  在上述代码中,首先在类上使用@ControllerAdvice注解,表明该类用于全局处理(和直接在Controller中使用@ExceptionHandler注解相比,可以实现跨Controller处理),然后在异常处理的方法上再通过@ExceptionHandler注解,实现特定异常处理器的拦截。

二、原理探究

  使用@ControllerAdvice+@ExceptionHandler注解实现异常处理的用法很简单,那么它们是如何生效的呢?我们下面逐步分析。

2.1、初始化

  在项目启动阶段(我们这里使用了基于SpringBoot方式运行),SpringBoot的自动配置类就会被加载,这个过程中就会把ExceptionHandlerExceptionResolver进行初始化,过程如下:

  首先,SpringBoot启动过程中,会加载自动配置类WebMvcAutoConfiguration,代码如下:

@Configuration(proxyBeanMethods = false)
@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(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
		//省略……
	}
}

  在静态内部类WebMvcAutoConfigurationAdapter上,又通过通过@Import注解引入了EnableWebMvcConfiguration配置类,而ExceptionHandlerExceptionResolver的初始化就是在该配置类中进行的,如下所示:

EnableWebMvcConfiguration配置类,也是WebMvcAutoConfiguration类的内部类。

@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {

	// 省略……
	
	@Override
	protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {
		if (this.mvcRegistrations != null && this.mvcRegistrations.getExceptionHandlerExceptionResolver() != null) {
			return this.mvcRegistrations.getExceptionHandlerExceptionResolver();
		}
		return super.createExceptionHandlerExceptionResolver();
	}
	//增强ExceptionHandlerExceptionResolver 类,主要扩展日志能力
	@Override
	protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
		super.extendHandlerExceptionResolvers(exceptionResolvers);
		if (this.mvcProperties.isLogResolvedException()) {
			for (HandlerExceptionResolver resolver : exceptionResolvers) {
				if (resolver instanceof AbstractHandlerExceptionResolver) {
					((AbstractHandlerExceptionResolver) resolver).setWarnLogCategory(resolver.getClass().getName());
				}
			}
		}
	}
}

  在EnableWebMvcConfiguration 内部类中提供了createExceptionHandlerExceptionResolver()方法用于创建ExceptionHandlerExceptionResolver 对象,但是该方法是什么时候被调用的呢?其实是在祖先类WebMvcConfigurationSupport(EnableWebMvcConfiguration->DelegatingWebMvcConfiguration->WebMvcConfigurationSupport,)中实现初始化调用的,代码如下:

@Bean
public HandlerExceptionResolver handlerExceptionResolver(
		@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
	List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
	configureHandlerExceptionResolvers(exceptionResolvers);
	if (exceptionResolvers.isEmpty()) {
		addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
	}
	extendHandlerExceptionResolvers(exceptionResolvers);
	HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
	composite.setOrder(0);
	composite.setExceptionResolvers(exceptionResolvers);
	return composite;
}

  上述方法被@Bean注解,所以在初始化时,会创建HandlerExceptionResolver 对象,这里实际上创建的是HandlerExceptionResolverComposite 对象,用于组合使用各种HandlerExceptionResolver对象。其中configureHandlerExceptionResolvers()和extendHandlerExceptionResolvers()两个方法都是空方法,主要用于扩展,而addDefaultHandlerExceptionResolvers()方法用于创建各种HandlerExceptionResolver实例,具体实现如下:

protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
		ContentNegotiationManager mvcContentNegotiationManager) {

	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);
	}
	exceptionHandlerResolver.afterPropertiesSet();
	exceptionResolvers.add(exceptionHandlerResolver);

	ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
	responseStatusResolver.setMessageSource(this.applicationContext);
	exceptionResolvers.add(responseStatusResolver);

	exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}

  在添加异常处理器的过程中,默认会创建ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver 和DefaultHandlerExceptionResolver三个。其中创建ExceptionHandlerExceptionResolver异常处理器时,又会执行afterPropertiesSet()方法(实现InitializingBean接口),该方法用来完成ExceptionHandlerExceptionResolver实例的初始化,这里其实主要就是用来初始化@ControllerAdvice注解对应的实例,具体实现如下:

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

private void initExceptionHandlerAdviceCache() {
	if (getApplicationContext() == null) {
		return;
	}
	//查找@ControllerAdvice注解的实例
	List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
	for (ControllerAdviceBean adviceBean : adviceBeans) {
		Class<?> beanType = adviceBean.getBeanType();
		if (beanType == null) {
			throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
		}
		ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
		if (resolver.hasExceptionMappings()) {
			this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
		}
		if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
			this.responseBodyAdvice.add(adviceBean);
		}
	}

	if (logger.isDebugEnabled()) {
		int handlerSize = this.exceptionHandlerAdviceCache.size();
		int adviceSize = this.responseBodyAdvice.size();
		if (handlerSize == 0 && adviceSize == 0) {
			logger.debug("ControllerAdvice beans: none");
		}
		else {
			logger.debug("ControllerAdvice beans: " +
					handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
		}
	}
}

  在initExceptionHandlerAdviceCache()方法中,查找@ControllerAdvice注解的实例集合,并进行迭代,在迭代过程中,会根据beanType创建ExceptionHandlerMethodResolver对象。创建对象的过程中,会检测被@ExceptionHandler注解的方法,并把其添加到mappedMethods变量中,供后续使用,具体实现如下:

public ExceptionHandlerMethodResolver(Class<?> handlerType) {
	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) {
	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+@ExceptionHandler注解就被初始化了。

2.2、运行机制

  经过前面的过程,已经完成了初始化。那么,当出现异常时,异常处理器又是如何工作的呢?我们接下来一探究竟。

  为了一探究竟,我们从DispatcherServlet类的doDispatch()方法开始进行分析,该方法是SpringMVC进行请求处理的入口(该方法在doService()方法中被调用)。为了分析异常处理器是如何生效的,我们这里把其中相关的代码片段截取了出来,如下所示:

//DispatcherServlet类
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	
	//省略……
	
	Exception dispatchException = null;
	try {
	
	}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代码块会捕获异常,并赋值给dispatchException对象,当然如果没有异常发生,该对象为null,然后再调用processDispatchResult()方法继续执行相关逻辑(无论是否有异常都会执行)。

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
		@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
		@Nullable Exception exception) throws Exception {

	boolean errorView = false;

	if (exception != null) {
		if (exception instanceof ModelAndViewDefiningException) {
			logger.debug("ModelAndViewDefiningException encountered", exception);
			mv = ((ModelAndViewDefiningException) exception).getModelAndView();
		}
		else {
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
			mv = processHandlerException(request, response, handler, exception);
			errorView = (mv != null);
		}
	}

	// Did the handler return a view to render?
	if (mv != null && !mv.wasCleared()) {
		render(mv, request, response);
		if (errorView) {
			WebUtils.clearErrorRequestAttributes(request);
		}
	}
	else {
		if (logger.isTraceEnabled()) {
			logger.trace("No view rendering, null ModelAndView returned.");
		}
	}

	if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
		// Concurrent handling started during a forward
		return;
	}

	if (mappedHandler != null) {
		// Exception (if any) is already handled..
		mappedHandler.triggerAfterCompletion(request, response, null);
	}
}

  在processDispatchResult()方法中,首先判断是否发生了异常,即exception 是否为null,如果没有发生异常,直接执行后续的页面渲染等操作,否则就会执行异常处理逻辑:首先判断exception异常类型,如果是ModelAndViewDefiningException类型的异常,直接获取对应ModelAndView对象,然后交由后续的页面渲染逻辑等进行处理,否则就通过processHandlerException()方法进一步进行异常逻辑处理。

@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
		@Nullable Object handler, Exception ex) throws Exception {

	// Success and error responses may use different content types
	request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

	// Check registered HandlerExceptionResolvers...
	ModelAndView exMv = null;
	if (this.handlerExceptionResolvers != null) {
		for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
			exMv = resolver.resolveException(request, response, handler, ex);
			if (exMv != null) {
				break;
			}
		}
	}
	if (exMv != null) {
		if (exMv.isEmpty()) {
			request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
			return null;
		}
		// We might still need view name translation for a plain error model...
		if (!exMv.hasView()) {
			String defaultViewName = getDefaultViewName(request);
			if (defaultViewName != null) {
				exMv.setViewName(defaultViewName);
			}
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Using resolved error view: " + exMv, ex);
		}
		else if (logger.isDebugEnabled()) {
			logger.debug("Using resolved error view: " + exMv);
		}
		WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
		return exMv;
	}

	throw ex;
}

  在processHandlerException()方法中,就使用到了前面初始化时创建的HandlerExceptionResolver实例了,默认使用了HandlerExceptionResolverComposite对象,即通过HandlerExceptionResolverComposite类的resolveException()方法进行异常处理,实现如下:

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

	if (this.resolvers != null) {
		for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
			ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
			if (mav != null) {
				return mav;
			}
		}
	}
	return null;
}

  因为HandlerExceptionResolverComposite就是为了组合各种异常处理器,所以在上述方法中,实际上还是迭代各个异常处理器的resolveException()方法进行处理,只要解析到第一个符合要求的ModelAndView 对象就直接返回。这里默认使用了ExceptionHandlerExceptionResolver实例对象,即通过该对象的resolveException()方法进行异常处理(实际在抽象类AbstractHandlerExceptionResolver中定义),具体实现如下:

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

	if (shouldApplyTo(request, handler)) {
		prepareResponse(ex, response);
		ModelAndView result = doResolveException(request, response, handler, ex);
		if (result != null) {
			// Print debug message when warn logger is not enabled.
			if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
				logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
			}
			// Explicitly configured warn logger in logException method.
			logException(ex, request);
		}
		return result;
	}
	else {
		return null;
	}
}

  在resolveException()方法中,又通过调用doResolveException()方法进行异常处理,该方法定义在AbstractHandlerMethodExceptionResolver类中,其中又调用了doResolveHandlerMethodException()方法,该方法定义在ExceptionHandlerExceptionResolver类中,实现如下:

//AbstractHandlerMethodExceptionResolver类
@Override
@Nullable
protected final ModelAndView doResolveException(
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

	return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
}

//ExceptionHandlerExceptionResolver类
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
		HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {

	ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
	if (exceptionHandlerMethod == null) {
		return null;
	}

	if (this.argumentResolvers != null) {
		exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
	}
	if (this.returnValueHandlers != null) {
		exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
	}

	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	ModelAndViewContainer mavContainer = new ModelAndViewContainer();

	try {
		if (logger.isDebugEnabled()) {
			logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
		}
		Throwable cause = exception.getCause();
		if (cause != null) {
			// Expose cause as provided argument as well
			exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
		}
		else {
			// Otherwise, just the given exception as-is
			exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
		}
	}
	catch (Throwable invocationEx) {
		if (invocationEx != exception && invocationEx != exception.getCause() && logger.isWarnEnabled()) {
			logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
		}
		return null;
	}
	if (mavContainer.isRequestHandled()) {
		return new ModelAndView();
	}else {
		ModelMap model = mavContainer.getModel();
		HttpStatus status = mavContainer.getStatus();
		ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
		mav.setViewName(mavContainer.getViewName());
		if (!mavContainer.isViewReference()) {
			mav.setView((View) mavContainer.getView());
		}
		if (model instanceof RedirectAttributes) {
			Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
			RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
		}
		return mav;
	}
}

  在doResolveHandlerMethodException()方法中,通过exceptionHandlerMethod的invokeAndHandle()方法就会调用到定义注解@ExceptionHandler的方法,然后根据mavContainer相关信息生成对应ModelAndView并进行返回,最后回到processDispatchResult()方法继续后续逻辑的执行。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
如果不想使用@ControllerAdvice注解,我们也可以通过实现HandlerExceptionResolver接口来实现全局异常处理。具体步骤如下: 1. 创建一个类,实现HandlerExceptionResolver接口。 2. 在类中实现resolveException方法,该方法会在全局异常发生时被执行。 3. 在resolveException方法中,我们可以根据不同的异常类型进行不同的处理,比如返回自定义错误信息或者跳转到指定页面。 4. 最后,我们需要将该类注册到Spring框架中,可以通过在配置文件中进行配置或者使用注解的方式进行注册。 以下是一个简单的示例: ```java public class GlobalExceptionHandler implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 返回自定义错误信息 ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("errorMsg", "系统错误,请稍后重试!"); modelAndView.setViewName("error"); return modelAndView; } } ``` 在上述代码中,我们实现了HandlerExceptionResolver接口并重写了resolveException方法。在该方法中,我们将错误信息存入ModelAndView中,并将viewName设置为"error",表示跳转到error页面显示错误信息。 最后,我们需要将该类注册到Spring框架中。可以通过在配置文件中进行配置或者使用注解的方式进行注册。例如,在Spring配置文件中添加如下配置: ```xml <bean id="globalExceptionHandler" class="com.example.GlobalExceptionHandler"/> ``` 在上述配置中,我们将GlobalExceptionHandler类注册为Spring的一个bean,并使用该类处理全局异常。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姠惢荇者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值