該系列檔案是本人在學習 Spring MVC 的原始碼程序中總結下來的,可能對讀者不太友好,請結合我的原始碼注釋 Spring MVC 原始碼分析 GitHub 地址 進行閱讀
Spring 版本:5.2.4.RELEASE
該系列其他檔案請查看:《精盡 Spring MVC 原始碼分析 - 文章導讀》
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.ExceptionHandlerExceptionResolverorg.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolverorg.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中
- 創建掃描該 ControllerAdviceBean 對應的型別
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 的設計者太膩害了~??
參考文章:芋道原始碼《精盡 Spring MVC 原始碼分析》
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/238658.html
標籤:Java
