錯誤場景
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAjM2EzLcd3LcJzLcJzdllmVldWYtl2Pn5GcuADNjhjY2QmMkFDNzImYiVGOwcTZwEGOwIDZ2cDNwkTZvwFOyMjM1cTMtUGall3LcVmdhNXLwRHdo9CXt92YucWbpRWdvx2Yx5yazF2Lc9CX6MHc0RHaiojIsJye.png)
驗證請求的Token合法性的Filter。Token校驗失敗時,直接抛自定義異常,移交給Spring處理:
測試HTTP請求:
日志輸出如下:說明IllegalRequestExceptionHandler未生效。
why?
這就需要精通Spring異常處理流程了。
解析
當所有Filter被執行完畢,Spring才會處理Servlet相關,而DispatcherServlet才是整個Servlet處理核心,它是前端控制器設計模式,提供 Spring Web MVC 的集中通路點并負責職責的分派。
在這,Spring處理了請求和處理器的對應關系及統一異常處理。
Filter内異常無法被統一處理,就是因為異常處理發生在 DispatcherServlet#doDispatch()
但此時,過濾器已全部執行完。
Spring異常統一處理
ControllerAdvice如何被Spring加載并對外暴露?
WebMvcConfigurationSupport#handlerExceptionResolver()
執行個體化并注冊一個ExceptionHandlerExceptionResolver 的執行個體
最終按下圖調用棧,Spring 執行個體化了ExceptionHandlerExceptionResolver類。
ExceptionHandlerExceptionResolver實作了InitializingBean
重寫 afterPropertiesSet()
initExceptionHandlerAdviceCache
完成所有 ControllerAdvice 中的ExceptionHandler 初始化:查找所有 @ControllerAdvice 注解的 Bean,把它們放入exceptionHandlerAdviceCache。
這裡即指自定義的IllegalRequestExceptionHandler
所有被 @ControllerAdvice 注解的異常處理器,都會在 ExceptionHandlerExceptionResolver 執行個體化時自動掃描并裝載在其exceptionHandlerAdviceCache。
initHandlerExceptionResolvers
當第一次請求發生時,DispatcherServlet#initHandlerExceptionResolvers() 将擷取所有注冊到 Spring 的 HandlerExceptionResolver 執行個體(ExceptionHandlerExceptionResolver正是),存到handlerExceptionResolvers
ControllerAdvice如何被Spring消費并處理異常?
DispatcherServlet
doDispatch()
執行使用者請求時,當查找、執行請求對應的 handler 過程中異常時:
- 會把異常值賦給 dispatchException
- 再移交 processDispatchResult()
processDispatchResult
當Exception非空時,繼續移交
processHandlerException
從 handlerExceptionResolvers 擷取有效的異常解析器以解析異常。
這裡的 handlerExceptionResolvers 一定包含聲明的IllegalRequestExceptionHandler#IllegalRequestException 的異常處理器的 ExceptionHandlerExceptionResolver 包裝類。
修正
為利用到 Spring MVC 的異常處理機制,改造Filter:
- 手動捕獲異常
- 将異常通過 HandlerExceptionResolver 進行解析處理
據此,修改 PermissionFilter,注入 HandlerExceptionResolver:
然後,在 doFilter 捕獲異常并移交 HandlerExceptionResolver:
現在再用錯誤 Token 請求,日志輸出如下:
響應體: