正文
1. 概述
上一篇文章
Spring Boot系列十 Spring MVC全局异常处理总结
介绍了如何在Spring MVC中实现对异常的处理,本文从源码角度理解Spring MVC异常处理原理,主要包括如下内容:
-
HandlerExceptionResolver以及常用实现类,理解默认实现HandlerExceptionResolver的用处和源码解读
-
Ordered接口及如何自定义HandlerExceptionResolver
-
Spring MVC 异常处理HandlerExceptionResolver对象初始化和处理流程的源码解读
2. HandlerExceptionResolver以及常用实现类
2.1 HandlerExceptionResolver接口
HandlerExceptionResolver是一个接口,用于处理网络请求过程中抛出的异常,但是不处理异常本身抛出的异常和视图解析过程中抛出的异常
下图是Spring MVC默认实现的HandlerExceptionResolver类
2.2. HandlerExceptionResolverComposite
Spring Boot启动时会默认注册HandlerExceptionResolverComposite对象。此类只是一个组合类,并不进行真正的异常处理。当他捕获异常时他只是将异常轮询委托给注册到它属性里的上的HandlerExceptionResolver类来处理异常,如果处理的结果不为null,则转给下一个处理
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
if (resolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : resolvers) {
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
默认注册到HandlerExceptionResolverComposite 的属性有以下3个HandlerExceptionResolver,按照优先级排列如下:
-
ExceptionHandlerExceptionResolver
-
ResponseStatusExceptionResolver
-
DefaultHandlerExceptionResolver
下面详细介绍这3个HandlerExceptionResolver的作用
2.3. ExceptionHandlerExceptionResolver
使用@ExceptionHandler注解方法处理异常类,我们之前介绍的使用注解处理异常就有这个类的功劳。默认情况下,这个HandlerExceptionResolver的优先级是最高。
以下是ExceptionHandlerExceptionResolver运行时属性值
属性exceptionHandlerAdviceCache :存储@Controller里@ExceptionHandler的方法
属性exceptionHandlerAdviceCache:存储@ControllerAdvice里@ExceptionHandler的全局方法
处理异常的关键代码
入口doResolveHandlerMethodException方法会通过 getExceptionHandlerMethod获取对应的@ExceptionHandler方法,如果有找到则执行此方法
@Override
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {
// 杳找对应的方法@ExceptionHandler
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
....
if (cause != null) {
// 执行异常处理方法
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
}
else {
// 执行异常处理方法
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
}
}
....
}
getExceptionHandlerMethod方法:查找特定异常的@ExceptionHandler方法,首先从抛出异常的@Controller类中寻找对应的处理方法,如果没有再从@ControllerAdvice中查找全局的@ExceptionHandler方法,如果找到,则调用这个方法执行处理,否则返回null
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
if (handlerMethod != null) {
// 从抛出异常的@Controller类中自身中寻找对应的处理方法,如果有找到先缓存
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);
}
}
// 如果没有再从@ControllerAdvice中查找全局的@ExceptionHandler方法,如果找到,则调用这个方法执行处理
for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
}
}
}
return null;
}
2.4. ResponseStatusExceptionResolver
使用@ResponseStatus处理异常,将异常转化对应的HTTP的状态码。@ResponseStatus可以定义在Excpetion的子类的类上,也可以定义在被@ExceptionHandler注解的方法上(不过这个需要小心使用,由于ExceptionHandlerExceptionResolver的优先级高,这种方式可能被ExceptionHandlerExceptionResolver覆盖掉)
异常处理入口doResolveException方法会先查找异常上的@ResponseStatus注解信息,如果有ResponseStatus ,则按照ResponseStatus 配置的值处理
//
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
// 获取异常的@ResponseStatus注解信息
ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
if (responseStatus != null) {
try {
// 如果有ResponseStatus ,则按照ResponseStatus 配置的值处理
return resolveResponseStatus(responseStatus, request, response, handler, ex);
}
catch (Exception resolveEx) {
logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx);
}
}
else if (ex.getCause() instanceof Exception) {
…
}
return null;
}
根据ResponseStatus 的值设置返回的http状态码和原因
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) throws Exception {
int statusCode = responseStatus.code().value();
String reason = responseStatus.reason();
if (!StringUtils.hasLength(reason)) {
// 设置返回的http状态码
response.sendError(statusCode);
}
else {
String resolvedReason = (this.messageSource != null ?
this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :
reason);
// 设置返回的http状态码和原因
response.sendError(statusCode, resolvedReason);
}
return new ModelAndView();
}
2.5. DefaultHandlerExceptionResolver
默认的HandlerExceptionResolver,将特定异常转化为标准的HTTP的状态码。
详细如下:左边是异常名称,右边是http的状态码
通过代码解释此类行为, 只列出NoSuchRequestHandlingMethodException相关的转换http错误码的代码,表格里其他异常处理类似
异常处理入口doResolveException方法,如果发现异常是NoSuchRequestHandlingMethodException,则调用方法handleNoSuchRequestHandlingMethod
// 对于NoSuchRequestHandlingMethodException进行转化http错误大码
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
try {
if (ex instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) {
return handleNoSuchRequestHandlingMethod((org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) ex,
request, response, handler);
}
else if (ex instanceof HttpRequestMethodNotSupportedException) {
…
}else if
…
}
handleNoSuchRequestHandlingMethod方法返回404错误码和错误信息
protected ModelAndView handleNoSuchRequestHandlingMethod(org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException ex,
HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
pageNotFoundLogger.warn(ex.getMessage());
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return new ModelAndView();
}
2.6. Ordered和实现自定义HandlerExceptionResolver类
每个具体的HandlerExceptionResolver都会实现Ordered接口,来定义执行的顺序,order值越小,越是优先执行。 如果要实现自己HandlerExceptionResolver,只需要满足两个条件: