异常处理好,每天睡的早


异常是避免不了的的。我们能做的就是减少异常, 以及做好异常的处理。

本文说说SpringMVC中的异常处理。

1.场景分析

我们在写业务代码时,多多少少会自发的做一些异常的捕获,或者处理。
这里有两个问题:

  • 你不可能处理到大多数异常
  • 大量的try catch代码充斥在业务代码中

2.Servlet+tomcat时代

在Servlet+tomcat时代, 异常的统一处理通过在web.xml中配置的。

<!-- 根据状态码 -->
<error-page>
    <error-code>500</error-code>
    <location>/500.jsp</location>
</error-page>

<!-- 根据异常类型 -->
<error-page>
	<exception-type>java.lang.RuntimeException</exception-type>
	<location>/500.jsp</location>
</error-page>

3.springmvc时代

在SpringMVC时代,得意于SpringMVC的中心化思想,所有请求走DispatcherServlet分发器。在DispatcherServlet里可以做一个统一的异常捕获与处理。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				分发请求,去调用业务逻辑
			}
			catch (Exception ex) {
				dispatchException = ex;//捕获异常
			}
			catch (Throwable err) {//捕获错误
				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 {
			if (asyncManager.isConcurrentHandlingStarted()) {
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

处理流程:

  1. 当业务代码抛出异常时,被捕获后,赋值给一个dispatchException变量。然后调用processDispatchResult去处理
  2. processDispatchResult中,异常的处理是调用processHandlerException(request, response, handler, exception)方法。此方法内会遍历所有的异常处理器,找到一个可以处理当前异常的异常处理器
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			Object handler, Exception ex) throws Exception {
			
		for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
			exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
			if (exMv != null) {
				break;
			}
		}
}

可以看到异常的处理是由SpringMVC的一个组件HandlerExceptionResolver来处理的。

这就引出了本文的中心,异常处理器HandlerExceptionResolver

4.核心HandlerExceptionResolver

此组件只专门用来处理异常的组件,

HandlerExceptionResolver接口只有一个异常解析的方法定义。

ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);

springMVC为其提供了多个实现
享读SpringMVC源码5-异常处理HandlerExceptionResolver_Java
这里主要的是AbstractHandlerExceptionResolver分支下,是处理异常的各个实现类:
AbstractHandlerExceptionResolver:
此抽象类,不但实现了resolveException方法,而且还提供了额外的方法

  • shouldApplyTo : 匹配逻辑,用于判断当前异常处理器支持那种类型的异常处理
4.1分类:
  • SimpleMappingExceptionResolver:简单映射,异常处理。根据异常类型匹配到一个视图处理
  • ResponseStatusExceptionResolver:若抛出的异常类型上有@ResponseStatus注解,此处理器会处理,根据注解的内容,返回相应的HTTP Status Code和内容给客户端【@ResponseStatus标注在异常类上此处理器才会处理】
  • AnnotationMethodHandlerExceptionResolver和ExceptionHandlerExceptionResolver:用来支持ExceptionHandler注解,使用被ExceptionHandler注解所标记的方法来处理异常。其中AnnotationMethodHandlerExceptionResolver在3.0版本中开始提供,ExceptionHandlerExceptionResolver在3.1版本中开始提供,从3.2版本开始,Spring推荐使用ExceptionHandlerExceptionResolver。高阶用法
  • DefaultHandlerExceptionResolver:默认的异常处理器,能处理springMVC大部分异常,也是springmvc默认装配的异常处理器。最常用

https://www.cnblogs.com/xinzhao/p/4902295.html

4.2异常处理器的初始化

异常的初始化伴随着DispatcherServlet的初始化

DispatcherServlet
protected void initStrategies(ApplicationContext context) {
		。。。
		initHandlerExceptionResolvers(context);
		。。。
}
private void initHandlerExceptionResolvers(ApplicationContext context) {
		this.handlerExceptionResolvers = null;

		if (this.detectAllHandlerExceptionResolvers) {
			// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
					.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
				// We keep HandlerExceptionResolvers in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
			}
		}
		else {
			try {
				HandlerExceptionResolver her =
						context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
				this.handlerExceptionResolvers = Collections.singletonList(her);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, no HandlerExceptionResolver is fine too.
			}
		}
		if (this.handlerExceptionResolvers == null) {
			this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
			}
		}
	}

异常初始化策略:
(1. detectAllHandlerExceptionResolvers 为true时从容器内获取所有实现了ExceptionResolver接口的bean,并根据其order属性排序,依次调用
(2.detectAllHandlerExceptionResolvers=false时,只获取handlerExceptionResolver的bean作为异常处理器
(3.上属两种还找不到异常处理器的情况下,默认加载配置文件中的处理器

默认的异常处理bean定义在DispatcherServlet.properties

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
4.3 自定义异常处理器

除了对springmvc标准异常的处理。有时候我们需要定义业务异常,来实现对异常更加精细的处理。此时,我们就要考虑自定义异常处理了。

自定义异常处理常用两种方式:

4.3.1 实现接口

很容易我们想到的就是实现HandlerExceptionResolver来处理异常

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        // 自定义异常处理器一般请放在首位
        exceptionResolvers.add(0, new AbstractHandlerExceptionResolver() {
            @Override
            protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
                // 若是自定义的业务异常,那就返回到单页面异常页面
                if (ex instanceof BusinessException) {
                    return new ModelAndView("/business.jsp");
                } else { // 否则统一到统一的错误页面
                    return new ModelAndView("/error.jsp");
                }
            }
        });
    }
}

我们看到返回的试图类型。当我们想直接返回json字符串怎么办??
1.response直接输出json , response.getWriter()相关方法
2.借助MappingJackson2JsonView

4.3.2 注解形式

在springboot流行的当下,注解的形式是最常用的自定义异常处理器方式。
@ExceptionHandler注解是spring3.0之后提供的异常处理相关的注解.

@ExceptionHandler 只能标注到一个方法上,被标注的方法就会成为一个异常处理器,处理指定的异常。

他的原理是什么呢?为啥一个方法标注了@ExceptionHandler注解就成为一个异常处理器了呢?

这都与上文提到的HandlerExceptionResolver接口的一个实现类ExceptionHandlerExceptionResolver类有关.

ExceptionHandlerExceptionResolver:

  • 1.类型支持
    通过shouldApplyTo方法我们可以看出此种类型的处理器只能处理HandlerMethod抛出的异常
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
		if (handler == null) {
			return super.shouldApplyTo(request, handler);
		}
		else if (handler instanceof HandlerMethod) {
			HandlerMethod handlerMethod = (HandlerMethod) handler;
			handler = handlerMethod.getBean();
			return super.shouldApplyTo(request, handler);
		}
		else {
			return false;
		}
	}
  • 2.异常处理
    通过调用链,此异常处理器的resolveException方法会调用此类的doResolveHandlerMethodException
protected ModelAndView doResolveHandlerMethodException{

		ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
		if (exceptionHandlerMethod == null) {
			return null;
		}
		...
		exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
}

protected ServletInvocableHandlerMethod getExceptionHandlerMethod(...) {
		//先在本类找可以处理异常的方法
		if (handlerMethod != null) {
			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);
			}
		
		}
		//找不到去切面类里去找
		for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
			ControllerAdviceBean advice = entry.getKey();
			if (advice.isApplicableToBeanType(handlerType)) {
				ExceptionHandlerMethodResolver resolver = entry.getValue();
				Method method = resolver.resolveMethod(exception);
				if (method != null) {
					return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
				}
			}
		}
}

可以看到,最终找到一个被@ExceptionHandler注解标示的method 封装成ServletInvocableHandlerMethod 调用执行。

@ExceptionHandler注解标示的method 所在的类,都会被封装成一个ExceptionHandlerMethodResolver。此解析器会根据异常类型,解析出对应的异常处理方法。

寻找过程:

  • 先看本类中有没有可以处理此异常的方法
  • 再从全局异常处理器中寻找。

ExceptionHandlerMethodResolver:异常处理方法解析器。目标:找到一个可以处理异常的方法。

这里有两种方式:一是从本Controller中寻找,另一个是从ControllerAdviceBean切面里找。

1.局部生效异常处理: 我们可以使用@ExceptionHandler注解在本类中标识一个方法,使其可以处理异常。他的初始化在此类异常第一次被处理时。封装一个此类对应的ExceptionHandlerMethodResolver,放入到ExceptionHandlerExceptionResolver.exceptionHandlerCache缓存中

2.全局异常处理:我们可以用@ControllerAdvice注解标示一个类是全局的异常处理。在此类中使用@ExceptionHandler定义各种各样的异常处理方法。此类会被封装成一个ExceptionHandlerMethodResolver,放入到ExceptionHandlerExceptionResolver.exceptionHandlerAdviceCache缓存中。他的初始化是在
ExceptionHandlerExceptionResolverbean初始化过程中afterPropertiesSet中初始化的

	@Override
	public void afterPropertiesSet() {
		// 初始化@ControllerAdvice标识的全局异常处理
		initExceptionHandlerAdviceCache();
	}
4.3.3 小结

@ExceptionHandler注解标识的方法所在的类,会被封装成一个ExceptionHandlerMethodResolver .ExceptionHandlerExceptionResolver 异常处理器,会找到一个ExceptionHandlerMethodResolverExceptionHandlerMethodResolver根据对应的异常类型找到一个处理方法进行处理

优先级顺序为:

  1. @Controller + @ExceptionHandler优先级最高
  2. @ControllerAdvice + @ExceptionHandler次之
  3. HandlerExceptionResolver最后(一般是DefaultHandlerExceptionResolver)

5.总结

推荐使用注解定义全局异常的形式处理异常,即@ControllerAdvice + @ExceptionHandler组合


如果本文任何错误,请批评指教,不胜感激 !
如果文章哪些点不懂,可以联系我交流学习!

微信公众号:源码行动
享学源码,行动起来,来源码行动

享读SpringMVC源码5-异常处理HandlerExceptionResolver_Java_02