SpringMVC的組件之HandlerExceptionResolver詳解
前言
在介紹完Handler、HandlerAdapter、HandlerMapping之后,剩下的比較關(guān)鍵的組件就是HandlerExceptionResolver、ViewResolver。
其他的像國際化、主題、文件上傳、重定向,這些錦上添花的組件都是一個框架需要關(guān)心的。
但不是我們平常使用的核心功能,所以有興趣的同學就自己了解吧。
HandlerExceptionResolver
不管是在處理請求映射(HandlerMapping),還是在請求被處理(Handler)時拋出的異常,DispatcherServlet都會委托給HandlerExceptionResolver進行異常處理。
該接口只有一個方法。
/**
* 嘗試處理handler執(zhí)行過程中拋出的異常。可能返回一個代表特定頁面的ModelAndView
* 如果返回的ModelAndView為空:{ModelAndView#isEmpty()},則表示該異常已經(jīng)被成功處理,并且不需要渲染視圖。例如:通過設置httpStatus處理異常.
* @param request 當前HTTP請求
* @param response 當前HTTP響應
* @param handler 被執(zhí)行的handler??赡転閚ull,如果異常發(fā)生在處理器選擇之前(例如:multipart處理失敗)
* @param ex 處理過程中拋出的異常
* @return 對應的ModelAndView。如果無法處理則返回null,DispatcherServlet將使用默認的處理流程。返回new ModelAndView(),則說明請求直接被處理完成了,不需要試圖處理。
*/
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);SpringMVC提供的異常處理器
| HandlerExceptionResolver | Description |
| SimpleMappingExceptionResolver | 將異常Class簡單的映射到錯誤視圖的名字。常用于瀏覽器應用渲染錯誤頁面 |
| DefaultHandlerExceptionResolver | 負責處理SpringMVC拋出的異常,并且將這些異常映射到對應的HTTP狀態(tài)碼??梢詫φ账奶娲撸篟esponseEntiryExceptionResolver、Controller Advice |
| ResponseStatusExceptionResolver | 通過@ResponseStatus注解來處理異常,并基于注解的值映射到HTTP狀態(tài)碼 |
| ExceptionHandlerExceptionResolver | .通過調(diào)用@Controller或者@ControllerAdvice中的@ExceptionHandler注解方法來處理異常 |
接下來,我們介紹一下比較常用的ExceptionHandlerExceptionResolver。
ExceptionHandlerExceptionResolver
如果有同學配置過全局異常處理的,應該會認識這兩注解:@ControllerAdvice @ExceptionHandler,而他們正是ExceptionHandlerExceptionResolver處理異常的重要抓手。
全局異常配置
在了解ExceptionHandlerExceptionResolver的設計之前,我們先來看看最常用的全局異常配置是怎樣的。只有知道他要達到什么樣的目標,才能理解他為什么這么設計/實現(xiàn)!
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public ResultDTO handleRuntimeException(RuntimeException e, HandlerMethod handlerMethod) {
// ...
}
/**
* 這是官方的例子
*/
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}好,現(xiàn)在來分析一下需求:
- 識別帶@ControllerAdvice注解的bean
- 識別這些bean中的@ExceptionHandler方法
- 根據(jù)@ExceptionHandler的條件,找到匹配的異常處理方法
- 識別和處理異常處理方法的參數(shù),并準備參數(shù)列表 PS: SpringMVC提供了豐富的可選參數(shù)
- 識別和處理方法返回值,并通過response給客戶端進行響應。
額,有沒有覺得似曾相識?對標一下@Controller、@RequestMapping?
| Handler領(lǐng)域 | Exception領(lǐng)域 | 作用/描述 |
| @Controller | @ControllerAdvice | 標記目標處理對象類 |
| @RestController | @RestControllerAdvice | 標記目標處理對象,并表示返回值即為要響應的消息體,通常會被json序列化 |
| @RequestMapping | @ExceptionHandler | 標記目標處理方法,并且包含方法可以處理的匹配條件 |
| 方法參數(shù)列表靈活多變,需要進行參數(shù)解析 | 相較于HandlerAdapter,支持的參數(shù)要少一些。例如:不支持@RequestBody參數(shù) | 方法參數(shù) |
| 方法返回值靈活多變, 需要進行返回值處理 | 相較于HandlerAdapter,支持的返回值要少一些。例如不支持ModelAndView,但支持ViewName,不支持異步響應返回值等等 | 方法返回值 |
是不是高度相似?這也意味著他們在參數(shù)解析和返回值處理上高度相似。
異常處理邏輯
與上面的全局異常處理相比,實際上Spring在處理異常時,還需要考慮@Controller中的@ExceptionHandler方法。
因此異常的處理分為兩部分
| 處理方法 | 描述 |
| @ControllerAdvice中的@ExceptionHandler | 這里面的方法是全局性的,所有的@RequestMapping方法只要發(fā)生異常且Controller中沒有聲明異常處理方法,則都會用這些方法處理 |
| @Controller中的@ExceptionHandler | 這些異常處理方法則只對該Controller有效。即,只能處理該Controller中的@RequestMapping方法異常 |
由此,我們將不得不有個優(yōu)先級,同樣也是最靠近@RequestMapping優(yōu)先。只有當對應的@Controller中沒有@ExceptionHandler時,才能用全局異常處理方法進行處理。 但是仔細考慮一下,在應用運行過程中,每個類都是固定的,方法也是固定的,方法有什么注解也是固定的。如果在調(diào)用時頻繁去使用反射遍歷所有的方法來獲取異常處理方法,是不是不太合理?首先,@Controller類很多時候都沒有異常處理方法,做這個遍歷操作純粹是無用功。其次,即使有異常處理方法,每次都遍歷所有方法也不合理,應該緩存起來。因此運行時類一般是不會變的。
Spring的設計
因為不管是@ControllerAdvice還是@Controller,解析@ExceptionHandler的方式都是一樣的,都要遍歷所有方法來尋找。因此可以統(tǒng)一起來。于是抽象出來ExceptionHandlerMethodResolver。先說明,別搞混了哈,我們今天說的ExceptionHandlerExceptionResolver是負責調(diào)用ExceptionHandler方法來處理異常的ExceptionResolver[異常處理器],而這個ExceptionHandlerMethodResolver負責解析@ExceptionHandler的MethodResolver[方法解析器],
- 他負責解析管理類中的@ExceptionHandler方法。mappedMethods可以理解為其異常處理方法的注冊中心。
- 由于異常可以嵌套,為了加速匹配,還搞了一個緩存exceptionLookupCache。該緩存使用的是Spring的ConcurrentReferenceHashMap,整個Entry都是軟引用,即發(fā)生OOM異常之前,key、value都會被清理。(key是異常類型,value是異常處理方法)
ExceptionHandlerExceptionResolver則需要統(tǒng)籌之前說的@ControllerAdvice和@Controller的異常處理。
- exceptionHandlerCache
- key是handlerType(HandlerMethod對應的@Controller對象類型),value是與之對應的ExceptionHandlerMethodResolver
- exceptionHandlerAdviceCache
- 緩存@ControllerAdvice對象的ExceptionHandlerMethodResolver,
- key是ControllerAdviceBean(他實際上封裝了@ControllerAdvice的類型信息,同時也可以通過beanFactory拿到相應的bean),value是與該對象對應的ExceptionHandlerMethodResolver
核心處理邏輯
核心處理邏輯在 ExceptionHandlerExceptionResolver#doResolveHandlerMethodException
/**
* 尋找一個@ExceptionHandler方法并調(diào)用他處理拋出的異常
*/
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
// 1. 獲取匹配的異常處理方法,并封裝成ServletInvocableHandlerMethod
// 就是這個方法控制著優(yōu)先使用@Controller中的@ExceptionHandler方法
// 他會首先檢查exceptionHandlerCache,然后才到exceptionHandlerAdviceCache。他們都是通過ExceptionHandler**Method**Resolver找到目標方法的。
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
// 沒有異常處理方法,則直接退出
if (exceptionHandlerMethod == null) {
return null;
}
// 初始化exceptionHandlerMethod。主要是設置參數(shù)解析器、返回值處理器
// 省略...
// 2. 準備exceptionHandlerMethod的調(diào)用參數(shù)
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
ArrayList<Throwable> exceptions = new ArrayList<>();
try {
// 遍歷嵌套異常作為方法參數(shù)
Throwable exToExpose = exception;
while (exToExpose != null) {
exceptions.add(exToExpose);
Throwable cause = exToExpose.getCause();
exToExpose = (cause != exToExpose ? cause : null);
}
Object[] arguments = new Object[exceptions.size() + 1];
exceptions.toArray(arguments); // efficient arraycopy call in ArrayList
arguments[arguments.length - 1] = handlerMethod;
// 調(diào)用exceptionHandlerMethod處理異常
// 該方法的調(diào)用就跟RequestMappingHandlerAdapter是一樣的了
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
}
catch (Throwable invocationEx) {
// 繼續(xù)默認的異常處理
return null;
}
if (mavContainer.isRequestHandled()) {
// 表示異常已被處理完成
return new ModelAndView();
}
else {
// 從ModelAndViewContainer封裝ModelAndView返回
// 省略...
return mav;
}
}總結(jié)
- 異常處理會優(yōu)先使用對應的@Controller中的@ExceptionHandler方法,然后才是@ControllerAdvice中的異常處理方法。
- 從宏觀層面,@ExceptionHandler的緩存分為兩層
| 層次 | 緩存所在 | 注冊中心或緩存 | 描述 |
| 類層面 | ExceptionHandlerExceptionResolver | 管理@ControllerAdvice的exceptionHandlerAdviceCache以及管理@Controller的exceptionHandlerCache | 每個BeanType對應一個ExceptionHandlerMethodResolver |
| 方法層 | ExceptionHandlerMethodResolver | 管理@ExceptionHandler方法的mappedMethods。負責加速尋找處理方法的exceptionLookupCache | 每個類都可能有多個@ExceptionHandler |
- 方法調(diào)用與RequestMappingHandlerAdapter一樣,都是通過ServletInvocableHandlerMethod進行處理。
如果理解了RequestMappingHandlerAdapter那么再來理解這個ExceptionHandlerExceptionResolver應該相對簡單些,只需要重點理解兩個點:
- @ExceptionHandler的出現(xiàn)的位置:@ControllerAdvice和@Controller。
- @ExceptionHandler的分層設計。
到此這篇關(guān)于SpringMVC的組件之HandlerExceptionResolver詳解的文章就介紹到這了,更多相關(guān)HandlerExceptionResolver詳解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 數(shù)據(jù)結(jié)構(gòu)之時間復雜度與空間復雜度詳解
算法復雜度分為時間復雜度和空間復雜度。其作用: 時間復雜度是度量算法執(zhí)行的時間長短;而空間復雜度是度量算法所需存儲空間的大小2021-11-11
在CentOS上安裝Java 17并實現(xiàn)多版本共存的詳細教程
在現(xiàn)代軟件開發(fā)中,Java 作為一種廣泛使用的編程語言,其版本更新頻繁,不同項目可能依賴不同版本的 Java 運行環(huán)境,CentOS 作為一款流行的 Linux 發(fā)行版,常被用于服務器部署和開發(fā)環(huán)境,本文將詳細介紹如何在 CentOS 上安裝 Java 17,并實現(xiàn)與現(xiàn)有 Java 8 的多版本共存2025-03-03
使用springcloud+oauth2攜帶token去請求其他服務
這篇文章主要介紹了使用springcloud+oauth2攜帶token去請求其他服務方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08

