【玩轉SpringBoot】讓錯誤處理重新由web服務器接管



其實web服務器是會處理錯誤的



web.xml還是隨處可見的年代時(確實有點老黃歷了),下面的這些配置應該都不陌生。

根據錯誤代碼處理錯誤,如下圖01:


根據異常類型處理錯誤,如下圖02:


不過我們更加熟悉的應該是SpringMVC的統一異常處理。如下圖03:


看到@ControllerAdvice注解和@ExceptionHandler注解都應該很熟悉吧。

處理原理就是在捕獲到業務Controller有異常拋出時,根據異常類型來這里找到對應的方法並執行。

可見,整個異常的處理過程都是在SpringMVC內部給搞定了,根本就沒有涉及到web服務器,如tomcat。

那么問題來了,明明web服務器可以處理異常,為啥SpringMVC還要自己處理呢?


是不想呢,還是另有苦衷


讀過本系列上一篇文章的小伙伴應該能猜出來一些。因為SpringMVC開發的工程最終打成war包,然后扔到tomcat下面即可。

而且SpringMVC和tomcat之間是通過Java Web的規范聯系起來的,它們之間根本沒有辦法自由交互的。

然而tomcat的錯誤處理需要在web.xml里面配置。嚴格來說,web.xml其實和SpringMVC關系不大。

特別是Spring全面進入Java和注解配置時代以后,web.xml逐漸被弱化,繼而變得可有可無,直到最終完全消失。

所以(我猜測)SpringMVC可能不希望自己的用戶到一個和自己關系不大的web.xml里面配置一些和業務相關的異常處理映射。

所以只好自己把異常處理消化掉。不勞tomcat大駕。因此才有了SpringMVC統一處理異常。


重新讓web服務器來處理錯誤


當歷史來到了SpringBoot的時代,SpringBoot翻身做主成了入口,web服務器竟然成了一個組件。

SpringBoot可以操作web服務器的API,通過編程的方式,對web服務器進行深度配置。

所以很多事情都變得容易起來,比如錯誤處理。

因為web.xml里的錯誤處理映射最終是注冊到tomcat里面了,所以SpringBoot只要操作tomcat的API,使用編程的方式也來注冊一些錯誤處理映射不就可以了嘛。

因為用戶直接是和SpringBoot打交道的,所以SpringBoot需要抽象出一套錯誤處理注冊機制,讓用戶來注冊。

這樣SpringBoot拿到用戶注冊的錯誤處理映射信息后,在生成web服務器(如tomcat)時,把這些映射信息添加到web服務器中即可。


SpringBoot注冊錯誤處理映射的方案


先來看看錯誤處理映射是如何描述的,如下圖04:


三個字段的含義是:

path是一個路徑,它表示的是錯誤處理的url。

status是一個狀態碼,如404、500等。

exception是一個異常類型,如LoginFailedException。

一共有三種使用方式:

1)status + path,如404 + /404,表示如果遇到了404,就去執行/404這個url。

2)exception + path,如LoginFailedException + /loginfailed,表示如果遇到登陸失敗異常,就去執行后面這個url

3)沒有status和exception,只有path,這相當於通配符,匹配所有異常情況。

接下來就該考慮如何注冊了,照例給個接口就行了,如下圖0506:


Spring是以bean打天下的,所以SpringBoot給的方法當然也是和bean相關。

只要向容器中注冊一個該接口類型的bean即可,如下圖07:


這些信息會被收集到並存好,如下圖08:


然后在創建web服務器時添加進去就行了,以tomcat為例,如下圖09:


可以看到最終轉換為tomcat的錯誤映射,如下圖10:


這里的setLocationsetErrorCodesetExceptionType三個setter方法,就對應於web.xml里的<location><error-code><exception-type>這三個標簽。

至此,錯誤處理已經被注冊好了。


SpringBoot仍需協助處理錯誤


有一點需要明白,SpringMVC交給tomcat的只是錯誤處理映射的匹配工作,但有些真正的錯誤處理還是要自己做的。

所以整個過程是這樣的,SpringMVC隨意拋出異常,這個異常會被拋到tomcat里面,tomcat獲取異常類型並根據注冊的錯誤處理映射關系找到一個url,然后調用這個url。

那么請問這個url指向哪里呢?很大概率又回到了SpringMVC里面了,是不是很有意思,哈哈。

tomcat就像一面鏡子,SpringMVC向它發射了一束光線,經過反射后又回來了。只不過發射的是一個異常,回來的是一個url。

這個url對應的是能夠處理這個錯誤的一個Controller的方法。這樣執行這個Controller方法就等於處理了異常。

這個Controller是一個能夠處理錯誤的Controller,所以就叫ErrorController如下圖11:


其實它主要是一個Marker接口,也就起一個標志作用。

下面是真正用於處理錯誤的Controller,實現了剛剛的標志接口,如下圖12:


@RequestMapping方法就是處理錯誤的,既可以返回JSON,也可以返回視圖頁面。

我們可以自己寫一個錯誤處理類,然后繼承這個類,添加自己的錯誤處理方法,最后使用@Controller注解重新注冊即可。

可以把一個異常映射成和它名稱一樣的url路徑,如把Exception映射為/Exception,如下圖13:


這兩個方法,一個返回html頁面,一個返回一個JSON。

備注異常類型和它映射的url之間的關系,可以按自己的需求去規划,統一規則即可。

處理錯誤時,自然要獲得錯誤相關信息才行,這個接口可以滿足,如下圖14:


可以獲得錯誤屬性和拋出的異常對象

可以看到接口的兩個方法都有WebRequest這個參數,說明錯誤信息是從request中獲取出來的。

同時也說明有些錯誤信息是有人專門放入request中的,是SpringBoot放的,還是web服務器放的?

其實都有,比如異常對象是SpringBoot放的,響應狀態碼是web服務器(按Java web規范)放的。

這也說明web服務器執行錯誤處理的url時用的是轉發(forward)而非重定向(redirect),因為要保留request中的信息。

還剩最后一個,錯誤視圖解析器,如下圖15:


SpringBoot自己提供了默認的視圖解析器實現,默認去classpath下面的error目錄下尋找.html視圖頁面。

如下圖16:


支持狀態碼到頁面的映射,如404默認映射為/error/404.html,500默認映射為/error/500.html。

如果沒有這么具體的頁面,還支持系列映射,如404映射為/error/4xx.html,500映射為5xx.html。

當然,默認的一般都無法滿足需求,我們可以繼承這個默認的類,然后重寫視圖解析方法。

最后把這個類注冊為bean即可,這樣SpringBoot就會使用我們的類了。如下圖17:



本文總結,重要


1)用戶使用SpringBoot提供的錯誤處理映射機制注冊狀態碼和異常的映射url信息。

2)這些映射信息最終會被注冊進web服務器中,如tomcat。

3)SpringBoot把異常拋給web服務器,web服務器根據異常找到對應的url,並執行它。

4)流程再次回到SpringBoot中,進入錯誤處理Controller的方法中,執行錯誤處理方法。

5)如果需要解析視圖的,使用錯誤視圖解析器進行視圖解析。否則就是直接返回JSON。

其中用戶能參與的就三步,注冊異常映射擴展錯誤處理Controller擴展錯誤視圖解析器

具體參與方式文章中都有,無非就是實現接口或繼承某個類,然后注冊為bean即可。

 

>>> 玩轉SpringBoot系列文章 <<<

 

【玩轉SpringBoot】配置文件yml的正確打開姿勢

【玩轉SpringBoot】用好條件相關注解,開啟自動配置之門

【玩轉SpringBoot】給自動配置來個整體大揭秘

【玩轉SpringBoot】看似復雜的Environment其實很簡單

【玩轉SpringBoot】翻身做主人,一統web服務器

 

>>> 品Spring系列文章 <<<

 

品Spring:帝國的基石

品Spring:bean定義上梁山

品Spring:實現bean定義時采用的“先進生產力”

品Spring:注解終於“成功上位”

品Spring:能工巧匠們對注解的“加持”

品Spring:SpringBoot和Spring到底有沒有本質的不同?

品Spring:負責bean定義注冊的兩個“排頭兵”

品Spring:SpringBoot輕松取勝bean定義注冊的“第一階段”

品Spring:SpringBoot發起bean定義注冊的“二次攻堅戰”

品Spring:注解之王@Configuration和它的一眾“小弟們”

品Spring:bean工廠后處理器的調用規則

品Spring:詳細解說bean后處理器

品Spring:對@PostConstruct和@PreDestroy注解的處理方法

品Spring:對@Resource注解的處理方法

品Spring:對@Autowired和@Value注解的處理方法

品Spring:真沒想到,三十步才能完成一個bean實例的創建

品Spring:關於@Scheduled定時任務的思考與探索,結果尷尬了

 

>>> 熱門文章集錦 <<<

 

畢業10年,我有話說

【面試】我是如何面試別人List相關知識的,深度有點長文

我是如何在畢業不久只用1年就升為開發組長的

爸爸又給Spring MVC生了個弟弟叫Spring WebFlux

【面試】我是如何在面試別人Spring事務時“套路”對方的

【面試】Spring事務面試考點吐血整理(建議珍藏)

【面試】我是如何在面試別人Redis相關知識時“軟懟”他的

【面試】吃透了這些Redis知識點,面試官一定覺得你很NB(干貨 | 建議珍藏)

【面試】如果你這樣回答“什么是線程安全”,面試官都會對你刮目相看(建議珍藏)

【面試】迄今為止把同步/異步/阻塞/非阻塞/BIO/NIO/AIO講的這么清楚的好文章(快快珍藏)

【面試】一篇文章幫你徹底搞清楚“I/O多路復用”和“異步I/O”的前世今生(深度好文,建議珍藏)

【面試】如果把線程當作一個人來對待,所有問題都瞬間明白了

Java多線程通關———基礎知識挑戰

品Spring:帝國的基石

 

作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號的二維碼,歡迎關注!

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM