异常处理好,每天睡的早
文章目录
异常是避免不了的的。我们能做的就是减少异常, 以及做好异常的处理。
本文说说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);
}
}
}
}
处理流程:
- 当业务代码抛出异常时,被捕获后,赋值给一个
dispatchException
变量。然后调用processDispatchResult
去处理 - 在
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为其提供了多个实现
这里主要的是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
缓存中。他的初始化是在ExceptionHandlerExceptionResolver
bean初始化过程中afterPropertiesSet
中初始化的
@Override
public void afterPropertiesSet() {
// 初始化@ControllerAdvice标识的全局异常处理
initExceptionHandlerAdviceCache();
}
4.3.3 小结
被@ExceptionHandler
注解标识的方法所在的类,会被封装成一个ExceptionHandlerMethodResolver
.ExceptionHandlerExceptionResolver 异常处理器,会找到一个ExceptionHandlerMethodResolver
,ExceptionHandlerMethodResolver
根据对应的异常类型找到一个处理方法进行处理
优先级顺序为:
- @Controller + @ExceptionHandler优先级最高
- @ControllerAdvice + @ExceptionHandler次之
- HandlerExceptionResolver最后(一般是DefaultHandlerExceptionResolver)
5.总结
推荐使用注解定义全局异常的形式处理异常,即@ControllerAdvice + @ExceptionHandler
组合
如果本文任何错误,请批评指教,不胜感激 !
如果文章哪些点不懂,可以联系我交流学习!
微信公众号:源码行动
享学源码,行动起来,来源码行动