精盡Spring MVC原始碼分析 - HandlerExceptionResolver 元件
阿新 • • 發佈:2020-12-22
> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀
>
> Spring 版本:5.2.4.RELEASE
>
> 該系列其他文件請檢視:[**《精盡 Spring MVC 原始碼分析 - 文章導讀》**](https://www.cnblogs.com/lifullmoon/p/14123963.html)
## HandlerExceptionResolver 元件
`HandlerExceptionResolver` 元件,處理器異常解析器,將處理器( `handler` )執行時發生的異常(也就是處理請求,執行方法的過程中)解析(轉換)成對應的 ModelAndView 結果
### 回顧
先來回顧一下在 `DispatcherServlet` 中處理請求的過程中哪裡使用到 `HandlerExceptionResolver` 元件,可以回到[**《一個請求的旅行過程》**](https://www.cnblogs.com/lifullmoon/p/14131862.html)中的 `DispatcherServlet` 的 `processHandlerException` 方法中看看,如下:
```java
@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...
// 遍歷 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;
}
}
}
// 情況一,生成了 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;
}
// 情況二,未生成 ModelAndView 物件,則丟擲異常
throw ex;
}
```
在 Spring MVC 的 `DispatcherServlet` 處理請求執行方法過程中,不管是否丟擲異常都會進行結果處理,如果丟擲了異常也需要呼叫該方法處理異常
可以看到,在 `` 處會遍歷所有的 `HandlerExceptionResolver` 異常處理器來處理,如果某一個處理器處理成功並返回 ModelAndView 物件,則直接返回
### HandlerExceptionResolver 介面
`org.springframework.web.servlet.HandlerExceptionResolver`,異常處理器介面,程式碼如下:
```java
public interface HandlerExceptionResolver {
/**
* 解析異常,轉換成對應的 ModelAndView 結果
*/
@Nullable
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
```
HandlerExceptionResolver 介面體系的結構如下:
### 初始化過程
在 `DispatcherServlet` 的 `initHandlerExceptionResolvers(ApplicationContext context)` 方法,初始化 HandlerExceptionResolver 元件,方法如下:
```java
private void initHandlerExceptionResolvers(ApplicationContext context) {
// 置空 handlerExceptionResolvers 處理
this.handlerExceptionResolvers = null;
// 情況一,自動掃描 HandlerExceptionResolver 型別的 Bean 們
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map 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");
}
}
}
```
1. 如果“開啟”探測功能,則掃描已註冊的 HandlerExceptionResolver 的 Bean 們,新增到 `handlerExceptionResolvers` 中,預設**開啟**
2. 如果“關閉”探測功能,則獲得 Bean 名稱為 "handlerExceptionResolver" 對應的 Bean ,將其新增至 `handlerExceptionResolvers`
3. 如果未獲得到,則獲得預設配置的 HandlerExceptionResolver 類,呼叫 `getDefaultStrategies(ApplicationContext context, Class strategyInterface)` 方法,就是從 `DispatcherServlet.properties` 檔案中讀取 HandlerExceptionResolver 的預設實現類,如下:
```properties
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 實現類
#### 構造方法
```java
public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered {
/**
* 異常解析器陣列
*/
@Nullable
private List resolvers;
/**
* 優先順序,預設最低
*/
private int order = Ordered.LOWEST_PRECEDENCE;
}
```
- `resolvers`:HandlerExceptionResolver 實現類列表
- `order`:優先順序,預設最低
從上面的初始化過程中可以看到,Spring Boot 預設配置下 HandlerExceptionResolverComposite 包含三個實現類:
1. `org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver`
2. `org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver`
3. `org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver`
#### resolveException
實現 `resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex)` 方法,遍歷 HandlerExceptionResolver 陣列,逐個處理異常 `ex`,如果成功,則返回 ModelAndView 物件,方法如下:
```java
@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 實現類的**基類**
#### 構造方法
```java
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` 處理器,方法如下:
```java
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);
}
```
1. 如果 `mappedHandlers` 包含該 `handler` 處理器物件,則返回 `true`
2. 如果 `mappedHandlerClasses` 包含該 `handler` 處理器所在類,則返回 `true`
3. 如果 `mappedHandlers` 和 `mappedHandlerClasses` 都為空,說明直接匹配
#### prepareResponse
`prepareResponse(Exception ex, HttpServletResponse response)` 方法,阻止響應快取,方法如下:
```java
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)` 方法,程式碼如下:
```java
@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);
```
1. 呼叫 `shouldApplyTo(HttpServletRequest request, Object handler)` 方法,判斷是否可以應用,如果可以應用
1. 呼叫 `prepareResponse(Exception ex, HttpServletResponse response)` 方法,阻止快取
2. 呼叫 `doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex)` 抽象方法,執行解析異常,返回 ModelAndView 物件
3. 如果 ModelAndView 物件非空,則列印日誌
4. 返回執行結果
2. 不可應用,直接返回 `null`
### AbstractHandlerMethodExceptionResolver
`org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver`,繼承 AbstractHandlerExceptionResolver 抽象類,基於 `handler` 型別為 **HandlerMethod** 的 HandlerExceptionResolver 抽象類。
可能你會有疑惑,為什麼 AbstractHandlerMethodExceptionResolver 只有一個 ExceptionHandlerExceptionResolver 子類,為什麼還要做抽象呢?因為 ExceptionHandlerExceptionResolver 是基於 `@ExceptionHandler` 註解來配置對應的異常處理器,而如果未來我們想自定義其它的方式來配置對應的異常處理器,就可以來繼承 AbstractHandlerMethodExceptionResolver 這個抽象類。