HandlerExceptionResolver 组件
HandlerExceptionResolver
组件,处理器异常解析器,将处理器( handler
)执行时发生的异常(也就是处理请求,执行方法的过程中)解析(转换)成对应的 ModelAndView 结果
回顾
先来回顾一下在 DispatcherServlet
中处理请求的过程中哪里使用到 HandlerExceptionResolver
组件,可以回到《一个请求的旅行过程》中的 DispatcherServlet
的 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
// 移除 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 属性
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
// <a> 遍历 HandlerExceptionResolver 数组,解析异常,生成 ModelAndView 对象
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
// 遍历 HandlerExceptionResolver 数组
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
// 解析异常,生成 ModelAndView 对象
exMv = resolver.resolveException(request, response, handler, ex);
// 生成成功,结束循环
if (exMv != null) {
break;
}
}
}
// <b> 情况一,生成了 ModelAndView 对象,进行返回
if (exMv != null) {
// ModelAndView 对象为空,则返回 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);
}
}
// 设置请求中的错误消息属性
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
// <c> 情况二,未生成 ModelAndView 对象,则抛出异常
throw ex;
}
在 Spring MVC 的 DispatcherServlet
处理请求执行方法过程中,不管是否抛出异常都会进行结果处理,如果抛出了异常也需要调用该方法处理异常
可以看到,在 <a>
处会遍历所有的 HandlerExceptionResolver
异常处理器来处理,如果某一个处理器处理成功并返回 ModelAndView 对象,则直接返回
HandlerExceptionResolver 接口
org.springframework.web.servlet.HandlerExceptionResolver
,异常处理器接口,代码如下:
public interface HandlerExceptionResolver {
/**
* 解析异常,转换成对应的 ModelAndView 结果
*/
@Nullable
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
HandlerExceptionResolver 接口体系的结构如下:
初始化过程
在 DispatcherServlet
的 initHandlerExceptionResolvers(ApplicationContext context)
方法,初始化 HandlerExceptionResolver 组件,方法如下:
private void initHandlerExceptionResolvers(ApplicationContext context) {
// 置空 handlerExceptionResolvers 处理
this.handlerExceptionResolvers = null;
// 情况一,自动扫描 HandlerExceptionResolver 类型的 Bean 们
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<>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
// 情况二,获得名字为 HANDLER_EXCEPTION_RESOLVER_BEAN_NAME 的 Bean
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.
}
}
// Ensure we have at least some HandlerExceptionResolvers, by registering
// default HandlerExceptionResolvers if no other resolvers are found.
/**
* 情况三,如果未获得到,则获得默认配置的 HandlerExceptionResolver 类
* {@link org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver}
* {@link org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver}
* {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver}
*/
if (this.handlerExceptionResolvers == null) {
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
- 如果“开启”探测功能,则扫描已注册的 HandlerExceptionResolver 的 Bean 们,添加到
handlerExceptionResolvers
中,默认开启 - 如果“关闭”探测功能,则获得 Bean 名称为 "handlerExceptionResolver" 对应的 Bean ,将其添加至
handlerExceptionResolvers
- 如果未获得到,则获得默认配置的 HandlerExceptionResolver 类,调用
getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface)
方法,就是从DispatcherServlet.properties
文件中读取 HandlerExceptionResolver 的默认实现类,如下:
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
在 Spring Boot 中,默认配置下会走上述 1
的逻辑,handlerExceptionResolvers
有两个元素:
org.springframework.boot.autoconfigure.web.DefaultErrorAttributes
:在 Spring Boot 中,逻辑比较简单,暂时忽略org.springframework.web.servlet.handler.HandlerExceptionResolverComposite
:复合的 HandlerExceptionResolver 实现类
接下来会对 HandlerExceptionResolverComposite
中的这三种异常处理器进行分析
HandlerExceptionResolverComposite
org.springframework.web.servlet.handler.HandlerExceptionResolverComposite
,实现 HandlerExceptionResolver、Ordered 接口,复合的 HandlerExceptionResolver 实现类
构造方法
public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered {
/**
* 异常解析器数组
*/
@Nullable
private List<HandlerExceptionResolver> resolvers;
/**
* 优先级,默认最低
*/
private int order = Ordered.LOWEST_PRECEDENCE;
}
resolvers
:HandlerExceptionResolver 实现类列表order
:优先级,默认最低
从上面的初始化过程中可以看到,Spring Boot 默认配置下 HandlerExceptionResolverComposite 包含三个实现类:
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
resolveException
实现 resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex)
方法,遍历 HandlerExceptionResolver 数组,逐个处理异常 ex
,如果成功,则返回 ModelAndView 对象,方法如下:
@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;
}
AbstractHandlerExceptionResolver
org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver
,实现 HandlerExceptionResolver、Ordered 接口,HandlerExceptionResolver 抽象类,作为所有 HandlerExceptionResolver 实现类的基类
构造方法
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
private static final String HEADER_CACHE_CONTROL = "Cache-Control";
/**
* 优先级,默认最低
*/
private int order = Ordered.LOWEST_PRECEDENCE;
/**
* 匹配的处理器对象的集合
*/
@Nullable
private Set<?> mappedHandlers;
/**
* 匹配的处理器类型的数组
*/
@Nullable
private Class<?>[] mappedHandlerClasses;
/**
* 防止响应缓存
*/
private boolean preventResponseCaching = false;
}
上面的这些属性在后续方法中会讲到
shouldApplyTo
shouldApplyTo(HttpServletRequest request, Object handler)
方法,判断当前 HandlerExceptionResolver 是否能应用到传入的 handler
处理器,方法如下:
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
if (handler != null) {
// <1> 如果 mappedHandlers 包含 handler 对象,则返回 true
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
return true;
}
// <2> 如果 mappedHandlerClasses 包含 handler 的类型,则返回 true
if (this.mappedHandlerClasses != null) {
for (Class<?> handlerClass : this.mappedHandlerClasses) {
if (handlerClass.isInstance(handler)) {
return true;
}
}
}
}
// Else only apply if there are no explicit handler mappings.
// <3> 如果 mappedHandlers 和 mappedHandlerClasses 都为空,说明直接匹配
return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}
- 如果
mappedHandlers
包含该handler
处理器对象,则返回true
- 如果
mappedHandlerClasses
包含该handler
处理器所在类,则返回true
- 如果
mappedHandlers
和mappedHandlerClasses
都为空,说明直接匹配
prepareResponse
prepareResponse(Exception ex, HttpServletResponse response)
方法,阻止响应缓存,方法如下:
protected void prepareResponse(Exception ex, HttpServletResponse response) {
if (this.preventResponseCaching) {
preventCaching(response);
}
}
/**
* Prevents the response from being cached, through setting corresponding
* HTTP {@code Cache-Control: no-store} header.
* @param response current HTTP response
*/
protected void preventCaching(HttpServletResponse response) {
response.addHeader(HEADER_CACHE_CONTROL, "no-store");
}
如果想要阻止响应缓存,需要设置 preventResponseCaching
为 true
resolveException
实现 resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,代码如下:
@Override
@Nullable
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) {
// <1> 判断是否可以应用
if (shouldApplyTo(request, handler)) {
// <1.1> 阻止缓存
prepareResponse(ex, response);
// <1.2> 执行解析异常,返回 ModelAndView 对象
ModelAndView result = doResolveException(request, response, handler, ex);
// <1.3> 如果 ModelAndView 对象非空,则打印日志
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);
}
// <1.4> 返回执行结果
return result;
}
// <2> 不可应用,直接返回 null
else {
return null;
}
}
@Nullable
protected abstract ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
- 调用
shouldApplyTo(HttpServletRequest request, Object handler)
方法,判断是否可以应用,如果可以应用
- 调用
prepareResponse(Exception ex, HttpServletResponse response)
方法,阻止缓存 - 调用
doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex)
抽象方法,执行解析异常,返回 ModelAndView 对象 - 如果 ModelAndView 对象非空,则打印日志
- 返回执行结果
- 不可应用,直接返回
null
AbstractHandlerMethodExceptionResolver
org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver
,继承 AbstractHandlerExceptionResolver 抽象类,基于 handler
类型为 HandlerMethod 的 HandlerExceptionResolver 抽象类。
可能你会有疑惑,为什么 AbstractHandlerMethodExceptionResolver 只有一个 ExceptionHandlerExceptionResolver 子类,为什么还要做抽象呢?因为 ExceptionHandlerExceptionResolver 是基于 @ExceptionHandler
注解来配置对应的异常处理器,而如果未来我们想自定义其它的方式来配置对应的异常处理器,就可以来继承 AbstractHandlerMethodExceptionResolver 这个抽象类。😈
有没发现 Spring MVC 中,存在大量的逻辑与配置分离的分层实现,嘻嘻~:happy:
shouldApplyTo
重写 shouldApplyTo(HttpServletRequest request, Object handler)
方法,代码如下:
@Override
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
// 情况一,如果 handler 为空,则直接调用父方法
if (handler == null) {
return super.shouldApplyTo(request, null);
}
// 情况二,处理 handler 为 HandlerMethod 类型的情况
else if (handler instanceof HandlerMethod) {
// <x> 获得真正的 handler
HandlerMethod handlerMethod = (HandlerMethod) handler;
handler = handlerMethod.getBean();
// 调用父方法
return super.shouldApplyTo(request, handler);
}
// 情况三,直接返回 false
else {
return false;
}
}
重点在于情况二,需要在 <x>
处,调用 HandlerMethod#getBean()
方法,获得真正的 handler
处理器。
doResolveException
重写 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,代码如下:
@Override
@Nullable
protected final ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) {
return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
}
@Nullable
protected abstract ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerMethod handlerMethod, Exception ex);
将 handler
转换成 HandlerMethod 类型,并提供新的抽象方法
【重点】ExceptionHandlerExceptionResolver
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
,实现 ApplicationContextAware、InitializingBean 接口,继承 AbstractHandlerMethodExceptionResolver 抽象类,基于 @ExceptionHandler
配置 HandlerMethod 的 HandlerExceptionResolver 实现类。
示例
可能你没有使用 @ExceptionHandler
注解来实现过异常的处理,例如:
@Log4j2
@RestControllerAdvice
public class CustomizeExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({EmptyArgumentException.class, IllegalArgumentException.class})
public Result<?> customizeHandleArgumentException(HttpServletRequest request, final Exception e, HttpServletResponse response) {
response.setStatus(HttpStatus.OK.value());
return Result.fail(ResultCode.PARAM_ERROR.getCode(), e.getMessage());
}
@ExceptionHandler({Exception.class})
public Result<?> customizeHandleException(HttpServletRequest request, final Exception e, HttpServletResponse response) {
log.error("异常拦截[{}]:", e.getMessage(), e);
response.setStatus(HttpStatus.OK.value());
return Result.fail(ResultCode.UNKNOWN.getCode(), e.getMessage());
}
}
该自定义异常处理类会处理 Controller
类抛出的指定类型的异常
构造方法
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
implements ApplicationContextAware, InitializingBean {
/**
* 自定义的方法参数处理器
*/
@Nullable
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
/**
* 方法参数处理器组合
*/
@Nullable
private HandlerMethodArgumentResolverComposite argumentResolvers;
/**
* 自定义的执行结果处理器
*/
@Nullable
private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
/**
* 执行结果处理器组合
*/
@Nullable
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
/*
* HTTP 消息转换器
*/
private List<HttpMessageConverter<?>> messageConverters;
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
/**
* 响应体的后置增强器
*/
private final List<Object> responseBodyAdvice = new ArrayList<>();
@Nullable
private ApplicationContext applicationContext;
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64);
private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<>();
public ExceptionHandlerExceptionResolver() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
// 初始化 messageConverters
this.messageConverters = new ArrayList<>();
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
try {
this.messageConverters.add(new SourceHttpMessageConverter<>());
} catch (Error err) {
// Ignore when no TransformerFactory implementation is available
}
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}
}
有没有一种熟悉的感觉,和 《HandlerAdapter 组件(一)之 HandlerAdapter》 的 RequestMappingHandlerAdapter 类似,有大量的相同变量,例如参数解析器和返回结果处理器,最终也是调用 ServletInvocableHandlerMethod 的方法。因为你定义也是定义的方法去处理相关的异常😈 往下看
afterPropertiesSet
因为 ExceptionHandlerExceptionResolver 实现了 InitializingBean 接口,在 Sping 初始化该 Bean 的时候,会调用该方法,完成一些初始化工作,方法如下:
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBodyAdvice beans
// 初始化 exceptionHandlerAdviceCache、responseBodyAdvice
initExceptionHandlerAdviceCache();
// 初始化 argumentResolvers 参数
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
// 初始化 returnValueHandlers 参数
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
- 调用
initExceptionHandlerAdviceCache()
方法,初始化exceptionHandlerAdviceCache
、responseBodyAdvice
,详情见下文 - 初始化
argumentResolvers
属性。其中,#getDefaultArgumentResolvers()
方法,获得默认的 HandlerMethodArgumentResolver 数组,详情见下文 - 初始化
returnValueHandlers
属性。其中,#getDefaultReturnValueHandlers()
方法,获得默认的 HandlerMethodReturnValueHandler 数组,详情见下文
initExceptionHandlerAdviceCache
initExceptionHandlerAdviceCache()
方法,初始化 exceptionHandlerAdviceCache
、responseBodyAdvice
,方法如下:
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
// <1> 扫描 @ControllerAdvice 注解的 Bean 们,并将进行排序
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(adviceBeans);
// <2> 遍历 ControllerAdviceBean 数组
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
// <2.1> 扫描该 ControllerAdviceBean 对应的类型
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
// <2.2> 有 @ExceptionHandler 注解,则添加到 exceptionHandlerAdviceCache 中
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
// <2.3> 如果该 beanType 类型是 ResponseBodyAdvice 子类,则添加到 responseBodyAdvice 中
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");
}
}
}
- 调用
ControllerAdviceBean
的findAnnotatedBeans(ApplicationContext context)
方法,扫描@ControllerAdvice
注解的 Bean 们,并将进行排序,这里就会扫描到上面示例中 CustomizeExceptionHandler 自定义异常处理类 - 遍历 ControllerAdviceBean 数组
- 创建扫描该 ControllerAdviceBean 对应的类型
ExceptionHandlerMethodResolver
对象resolver
,该对象在下面会分析 - 有
@ExceptionHandler
注解,则将resolver
添加到exceptionHandlerAdviceCache
中 - 如果该
beanType
类型是 ResponseBodyAdvice 子类,则添加到responseBodyAdvice
中
getDefaultArgumentResolvers
getDefaultArgumentResolvers()
方法,获得默认的 HandlerMethodArgumentResolver 数组,方法如下:
protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
// Annotation-based argument resolution
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
return resolvers;
}
getDefaultReturnValueHandlers
getDefaultReturnValueHandlers()
方法,获得默认的 HandlerMethodReturnValueHandler 数组,方法如下:
protected List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();
// Single-purpose return value types
handlers.add(new ModelAndViewMethodReturnValueHandler());
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(
getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
// Annotation-based return value types
handlers.add(new ModelAttributeMethodProcessor(false));
handlers.add(new RequestResponseBodyMethodProcessor(
getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
// Multi-purpose return value types
handlers.add(new ViewNameMethodReturnValueHandler());
handlers.add(new MapMethodProcessor());
// Custom return value types
if (getCustomReturnValueHandlers() != null) {
handlers.addAll(getCustomReturnValueHandlers());
}
// Catch-all
handlers.add(new ModelAttributeMethodProcessor(true));
return handlers;
}
ExceptionHandlerMethodResolver 类
在 ExceptionHandlerExceptionResolver 的
initExceptionHandlerAdviceCache
方法中会用到,两者的名字太容易混淆了
org.springframework.web.method.annotation.ExceptionHandlerMethodResolver
,添加 @ControllerAdvice
注解的 Bean,用于解析添加了 @ExceptionHandler
注解的方法
构造方法
public class ExceptionHandlerMethodResolver {
/**
* A filter for selecting {@code @ExceptionHandler} methods.
*
* MethodFilter 对象,用于过滤带有 @ExceptionHandler 注解的方法
*/
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
/**
* 已经映射的方法
*
* 在 {@link #ExceptionHandlerMethodResolver(Class)} 构造方法中初始化
*/
pivate final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
/**
* 已经匹配的方法
*
* 在 {@link #resolveMethod(Exception)} 方法中初始化
*/
private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
// <1> 遍历 @ExceptionHandler 注解的方法,这些方法用于处理对应的异常
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
// <2> 遍历处理的异常集合,获取到该方法能处理哪些异常
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
// <3> 添加到 mappedMethods 中
addExceptionMapping(exceptionType, method);
}
}
}
}
mappedMethods
和 exceptionLookupCache
差别在于,后者是经过查找,比较优先级之后所产生的
- 遍历
@ExceptionHandler
注解的方法 - 调用
detectExceptionMappings(Method method)
方法,获得方法的异常数组,如下:
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
List<Class<? extends Throwable>> result = new ArrayList<>();
// 首先,从方法上的 @ExceptionHandler 注解中,获得要处理的异常类型,添加到 result 中
detectAnnotationExceptionMappings(method, result);
// 其次,如果获取不到,从方法参数中,获得所处理的异常,添加到 result 中
if (result.isEmpty()) {
for (Class<?> paramType : method.getParameterTypes()) {
if (Throwable.class.isAssignableFrom(paramType)) {
result.add((Class<? extends Throwable>) paramType);
}
}
}
// 如果获取不到,则抛出 IllegalStateException 异常
if (result.isEmpty()) {
throw new IllegalStateException("No exception types mapped to " + method);
}
return result;
}
private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
Assert.state(ann != null, "No ExceptionHandler annotation");
result.addAll(Arrays.asList(ann.value()));
}
- 调用
addExceptionMapping(Class<? extends Throwable> exceptionType, Method method)
方法,添加到mappedMethods
中,如下:
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
// 添加到 mappedMethods 中
Method oldMethod = this.mappedMethods.put(exceptionType, method);
// 如果已存在,说明冲突,所以抛出 IllegalStateException 异常
if (oldMethod != null && !oldMethod.equals(method)) {
throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
exceptionType + "]: {" + oldMethod + ", " + method + "}");
}
}
hasExceptionMappings
hasExceptionMappings()
方法,判断 mappedMethods
非空,方法如下:
public boolean hasExceptionMappings() {
return !this.mappedMethods.isEmpty();
}
resolveMethod
resolveMethod(Exception exception)
方法,获取解析异常对应的方法,方法如下:
@Nullable
public Method resolveMethod(Exception exception) {
return resolveMethodByThrowable(exception);
}
@Nullable
public Method resolveMethodByThrowable(Throwable exception) {
// 首先,获得异常对应的方法
Method method = resolveMethodByExceptionType(exception.getClass());
// 其次,获取不到,则使用异常 cause 对应的方法
if (method == null) {
Throwable cause = exception.getCause();
if (cause != null) {
method = resolveMethodByExceptionType(cause.getClass());
}
}
return method;
}
按照 exception
和 exception.cause
的先后,调用 resolveMethodByExceptionType(Class<? extends Throwable> exceptionType)
方法,获得异常对应的方法,如下:
@Nullable
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
// 首先,先从 exceptionLookupCache 缓存中获得异常对应的处理方法
Method method = this.exceptionLookupCache.get(exceptionType);
// 其次,获取不到,则从 mappedMethods 中获得,并添加到 exceptionLookupCache 中
if (method == null) {
method = getMappedMethod(exceptionType);
this.exceptionLookupCache.put(exceptionType, method);
}
return method;
}
逻辑比较简单,调用 getMappedMethod(Class<? extends Throwable> exceptionType)
方法,获得异常对应的方法,如下:
@Nullable
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<>();
// 遍历 mappedMethods 数组,匹配异常,添加到 matches 中
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
// 将匹配的结果,排序,选择第一个
if (!matches.isEmpty()) {
matches.sort(new ExceptionDepthComparator(exceptionType));
return this.mappedMethods.get(matches.get(0));
}
else {
return null;
}
}
逻辑比较简单,关于 org.springframework.core.ExceptionDepthComparator
比较器,胖友自己点击 传送门 查看。大体的逻辑是,比较它们和目标类的继承层级,越小越匹配。
getExceptionHandlerMethod
getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception)
方法,获得异常对应的 ServletInvocableHandlerMethod 对象,代码如下:
@Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {
// 处理器的类型
Class<?> handlerType = null;
// <1> 首先,如果 handlerMethod 非空,则先获得 Controller 对应的 @ExceptionHandler 处理器对应的方法
if (handlerMethod != null) {
// Local exception handler methods on the controller class itself.
// To be invoked through the proxy, even in case of an interface-based proxy.
// 获得 handlerType
handlerType = handlerMethod.getBeanType();
// 获得 handlerType 对应的 ExceptionHandlerMethodResolver 对象
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
// 获得异常对应的 Method 处理方法
Method method = resolver.resolveMethod(exception);
// 如果获得该异常对应的 Method 处理方法,则创建 ServletInvocableHandlerMethod 对象,并返回
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
// For advice applicability check below (involving base packages, assignable types
// and annotation presence), use target class instead of interface-based proxy.
// 获得 handlerType 的原始类。因为,此处有可能是代理对象
if (Proxy.isProxyClass(handlerType)) {
handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
}
}
// <2> 其次,使用 ControllerAdvice 对应的 @ExceptionHandler 处理器对应的方法
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
ControllerAdviceBean advice = entry.getKey();
// 如果 ControllerAdvice 支持当前的 handlerType
if (advice.isApplicableToBeanType(handlerType)) {
// 获得 handlerType 对应的 ExceptionHandlerMethodResolver 对象
ExceptionHandlerMethodResolver resolver = entry.getValue();
// 获得异常对应的 Method 处理方法
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
}
}
}
// 最差,获取不到
return null;
}
- 首先,如果
handlerMethod
非空,则先获得 Controller 对应的@ExceptionHandler
处理器对应的方法,如果获取到了,则将该 Method 封装成 ServletInvocableHandlerMethod 对象并返回 - 其次,使用
ControllerAdvice
对应的@ExceptionHandler
处理器对应的方法,如果获取到了,则将该 Method 封装成 ServletInvocableHandlerMethod 对象并返回 - 最差,获取不到,返回
null
上面第 2
种情况也就是示例中定义的方法哦~
doResolveHandlerMethodException
实现 doResolveHandlerMethodException(ttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception exception)
方法,处理异常,代码如下:
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
// <1> 获得异常对应的 ServletInvocableHandlerMethod 对象
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
// <1.1> 设置 ServletInvocableHandlerMethod 对象的相关属性
if (this.argumentResolvers != null) {
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// <1.2> 创建 ServletWebRequest 对象
ServletWebRequest webRequest = new ServletWebRequest(request, response);
// <1.3> 创建 ModelAndViewContainer 对象
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
try {
if (logger.isDebugEnabled()) {
logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
}
// <2> // 执行处理该异常的方法 ServletInvocableHandlerMethod 的调用
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) {
// Any other than the original exception is unintended here,
// probably an accident (e.g. failed assertion or the like).
// <2.1> 发生异常,则直接返回
if (invocationEx != exception && logger.isWarnEnabled()) {
logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
}
// Continue with default processing of the original exception...
return null;
}
// <3> 如果 mavContainer 已处理,则返回 '空的' ModelAndView 对象。
if (mavContainer.isRequestHandled()) {
return new ModelAndView();
}
// <4> 如果 mavContainer 未处,则基于 `mavContainer` 生成 ModelAndView 对象
else {
ModelMap model = mavContainer.getModel();
HttpStatus status = mavContainer.getStatus();
// <4.1> 创建 ModelAndView 对象,并设置相关属性
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
mav.setViewName(mavContainer.getViewName());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
// <4.2>
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
}
- 调用
getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception)
方法,获得异常对应的 ServletInvocableHandlerMethod 对象
- 设置 ServletInvocableHandlerMethod 对象的相关属性,参数解析器,返回结果处理器
- 创建 ServletWebRequest 对象
webRequest
,封装了请求和响应 - 创建 ModelAndViewContainer 对象
mavContainer
,用于获取 ModelAndView 对象
- 执行处理该异常的方法,ServletInvocableHandlerMethod 对象的调用
- 在《HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》中已经详细的分析过了
- 此处传入了
Object... providedArgs
参数为exception
和handlerMethod
变量,这也是为什么@ExceptionHanlder
注解的方法,可以设置为这两个参数😈
- 发生异常,则直接返回
- 如果
mavContainer
已处理,则返回 “空的” ModelAndView 对象。😈 这样,就不会被后续的 ViewResolver 所处理。为什么呢?可以自己回看下 DispatcherServlet 的processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,很容易明白 - 如果
mavContainer
未处理,则基于mavContainer
生成 ModelAndView 对象
- 创建 ModelAndView 对象,并设置相关属性,视图名称
- FlashMapManager 相关,暂时忽略
ResponseStatusExceptionResolver
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver
,实现 MessageSourceAware 接口,继承 AbstractHandlerExceptionResolver 抽象类,基于 @ResponseStatus
提供错误响应的 HandlerExceptionResolver 实现类
构造方法
public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware {
@Nullable
private MessageSource messageSource;
}
applyStatusAndReason
applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response)
方法,设置错误响应,方法如下:
protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response)
throws IOException {
// 情况一,如果无错误提示,则响应只设置状态码
if (!StringUtils.hasLength(reason)) {
response.sendError(statusCode);
}
// 情况二,如果有错误信息,则响应设置状态码 + 错误提示
else {
// 进一步解析错误提示,如果有 messageSource 的情况下
String resolvedReason = (this.messageSource != null ?
this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :
reason);
// 设置
response.sendError(statusCode, resolvedReason);
}
// 创建“空” ModelAndView 对象,并返回
return new ModelAndView();
}
注意,此处返回的也是“空”的 ModelAndView 对象。这样,就不会被后续的 ViewResolver 所处理
doResolveException
实现 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,代码如下:
@Override
@Nullable
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) {
try {
// <1> 情况一,如果异常是 ResponseStatusException 类型,进行解析并设置到响应
if (ex instanceof ResponseStatusException) {
return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
}
// <2> 情况二,如果有 @ResponseStatus 注解,进行解析并设置到响应
ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
if (status != null) {
return resolveResponseStatus(status, request, response, handler, ex);
}
// <3> 情况三,使用异常的 cause 在走一次情况一、情况二的逻辑。
if (ex.getCause() instanceof Exception) {
return doResolveException(request, response, handler, (Exception) ex.getCause());
}
}
catch (Exception resolveEx) {
if (logger.isWarnEnabled()) {
logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx);
}
}
return null;
}
- 情况一,如果异常是 ResponseStatusException 类型,进行解析并设置到响应,调用
resolveResponseStatus(ResponseStatusException ex, HttpServletRequest request, HttpServletResponse response, Object handler)
方法,如下:
protected ModelAndView resolveResponseStatusException(ResponseStatusException ex,
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws Exception {
int statusCode = ex.getStatus().value();
String reason = ex.getReason();
return applyStatusAndReason(statusCode, reason, response);
}
- 情况二,如果有
@ResponseStatus
注解,进行解析并设置到响应,调用resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,如下:
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
int statusCode = responseStatus.code().value();
String reason = responseStatus.reason();
return applyStatusAndReason(statusCode, reason, response);
}
- 情况三,使用异常的
cause
再走一次情况一、情况二的逻辑
DefaultHandlerExceptionResolver
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
,继承 AbstractHandlerExceptionResolver 抽象类,默认 HandlerExceptionResolver 实现类,针对各种异常,设置错误响应码
其中,实现 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,代码如下:
@Override
@Nullable
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported(
(HttpRequestMethodNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported(
(HttpMediaTypeNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
return handleHttpMediaTypeNotAcceptable(
(HttpMediaTypeNotAcceptableException) ex, request, response, handler);
}
else if (ex instanceof MissingPathVariableException) {
return handleMissingPathVariable(
(MissingPathVariableException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestParameterException) {
return handleMissingServletRequestParameter(
(MissingServletRequestParameterException) ex, request, response, handler);
}
else if (ex instanceof ServletRequestBindingException) {
return handleServletRequestBindingException(
(ServletRequestBindingException) ex, request, response, handler);
}
else if (ex instanceof ConversionNotSupportedException) {
return handleConversionNotSupported(
(ConversionNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof TypeMismatchException) {
return handleTypeMismatch(
(TypeMismatchException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotReadableException) {
return handleHttpMessageNotReadable(
(HttpMessageNotReadableException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotWritableException) {
return handleHttpMessageNotWritable(
(HttpMessageNotWritableException) ex, request, response, handler);
}
else if (ex instanceof MethodArgumentNotValidException) {
return handleMethodArgumentNotValidException(
(MethodArgumentNotValidException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestPartException) {
return handleMissingServletRequestPartException(
(MissingServletRequestPartException) ex, request, response, handler);
}
else if (ex instanceof BindException) {
return handleBindException((BindException) ex, request, response, handler);
}
else if (ex instanceof NoHandlerFoundException) {
return handleNoHandlerFoundException(
(NoHandlerFoundException) ex, request, response, handler);
}
else if (ex instanceof AsyncRequestTimeoutException) {
return handleAsyncRequestTimeoutException(
(AsyncRequestTimeoutException) ex, request, response, handler);
}
}
catch (Exception handlerEx) {
if (logger.isWarnEnabled()) {
logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
}
}
return null;
}
逻辑不复杂,根据不同的异常,设置响应码和错误信息,例如 HTTP 方法类型不支持,如下:
protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {
String[] supportedMethods = ex.getSupportedMethods();
if (supportedMethods != null) {
response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
}
// 405 状态码,HTTP Method 不支持
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
return new ModelAndView();
}
注意,返回的都是“空”的 ModelAndView 对象。这样,就不会被后续的 ViewResolver 所处理
总结
本文对 Spring MVC 中的 HandlerExceptionResolver
组件进行分析,处理器异常解析器,将处理器( handler
)执行时发生的异常(也就是处理请求,执行方法的过程中发生的异常)解析(转换)成对应的 ModelAndView 结果
HandlerExceptionResolver
的实现类没有特别多,不过也采用了组合模式,如果某个异常处理器进行处理了,也就是返回的 ModeAndView 不为 null
(一般都是“空”对象),则直接返回该 ModeAndView 对象
在 Spring MVC 和 Spring Boot 中,默认情况下都有三种 HandlerExceptionResolver 实现类,他们的顺序如下:
ExceptionHandlerExceptionResolver
:基于@ExceptionHandler
配置 HandlerMethod 的 HandlerExceptionResolver 实现类。例如通过@ControllerAdvice
注解自定义异常处理器,加上@ExceptionHandler
注解指定方法所需要处理的异常类型,这种方式就在这个实现类中实现的。没有使用过这两个注解可以参考上面的示例ResponseStatusExceptionResolver
:基于@ResponseStatus
提供错误响应的 HandlerExceptionResolver 实现类。例如在方法上面添加@ResponseStatus
注解,指定该方法发生异常时,需要设置的code
响应码和reason
错误信息DefaultHandlerExceptionResolver
:默认 HandlerExceptionResolver 实现类,针对各种异常,设置错误响应码。例如 HTTP Method 不支持,则在这个实现类中往响应中设置错误码和错误信息
到这里,已经分析了 Spring MVC 的 DispatcherServlet,以及 MultipartResolver、HandlerMapping、HandlerAdapter 和 HandlerExceptionResolver 四个组件,只想说:Spring MVC 的设计者太腻害了~😛