最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

從拋出異常到返回JSON/XML:SpringBoot?異常處理全鏈路深度解析

 更新時間:2026年02月02日 11:21:25   作者:czlczl20020925  
Spring?Boot?(基于?Spring?MVC)?提供了一套優(yōu)雅的、解耦的異常處理機(jī)制,本文將帶你深入底層,探究當(dāng)一個異常被拋出后,究竟經(jīng)歷了怎樣的奇幻漂流,又是如何根據(jù)前端需求自動變成?JSON?或?XML?的,感興趣的朋友跟隨小編一起看看吧

前言

在 Web 開發(fā)中,異常處理是不可避免的一環(huán)。初學(xué)者往往喜歡在 Service 或 Controller 層寫大量的 try-catch 代碼,最后返回一個 Result 對象。這種做法雖然直觀,但會導(dǎo)致業(yè)務(wù)代碼與錯誤處理邏輯嚴(yán)重耦合,代碼極其臃腫。

Spring Boot (基于 Spring MVC) 提供了一套優(yōu)雅的、解耦的異常處理機(jī)制。本文將帶你深入底層,探究當(dāng)一個異常被拋出后,究竟經(jīng)歷了怎樣的奇幻漂流,又是如何根據(jù)前端需求自動變成 JSON 或 XML 的。

沒問題,為了讓你的博客內(nèi)容足夠硬核且具有實(shí)戰(zhàn)參考價值,我將這個異常處理流程進(jìn)行了大幅度的擴(kuò)充。

這次我們不再只停留在表面,而是結(jié)合“源碼級”的執(zhí)行步驟完整的代碼示例,把整個過程拆解得清清楚楚。

你可以直接使用以下內(nèi)容作為博客的核心章節(jié)。

Spring Boot 異常處理全鏈路深度解析

很多同學(xué)只會用 @ControllerAdvice,卻不知道當(dāng)一個異常拋出后,Spring Boot 內(nèi)部到底發(fā)生了什么。下面我們通過一個真實(shí)的業(yè)務(wù)場景,配合源碼視角,還原異常的“一生”。

1. 場景準(zhǔn)備:案發(fā)現(xiàn)場

首先,我們需要構(gòu)建一個標(biāo)準(zhǔn)的異常拋出場景。

1.1 定義標(biāo)準(zhǔn)響應(yīng)體 (Result)

這是企業(yè)級開發(fā)的標(biāo)配,前后端統(tǒng)一契約。

@Data
public class Result<T> {
    private Integer code;
    private String msg;
    private T data;
    public static <T> Result<T> fail(Integer code, String msg) {
        Result<T> r = new Result<>();
        r.code = code;
        r.msg = msg;
        return r;
    }
}

1.2 定義自定義異常 (MyException)

public class OrderNotFoundException extends RuntimeException {
    public OrderNotFoundException(String message) {
        super(message);
    }
}

1.3 編寫 Controller (肇事者)

@RestController
public class OrderController {
    @GetMapping("/order/{id}")
    public Result getOrder(@PathVariable Integer id) {
        if (id < 0) {
            // 【關(guān)鍵點(diǎn)】:這里拋出了異常,Controller 方法立即終止!
            throw new OrderNotFoundException("訂單ID不能為負(fù)數(shù)");
        }
        return new Result(); // 正常邏輯
    }
}

1.4 編寫全局異常處理器 (救援隊)

@RestControllerAdvice // 相當(dāng)于 @ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {
    @ExceptionHandler(OrderNotFoundException.class)
    public Result handleOrderException(OrderNotFoundException e) {
        // 捕獲異常,并“捏造”一個優(yōu)雅的 Result 返回
        return Result.fail(404, e.getMessage());
    }
}

2. 深度解析:異常處理的七步“奇幻漂流”

當(dāng)用戶請求 GET /order/-1 時,后臺發(fā)生了如下精密的操作:

第一步:異常冒泡 (JVM 層面)

Controller 的 getOrder 方法執(zhí)行到 throw 語句。此時,當(dāng)前方法棧幀被銷毀,Controller 徹底“掛了”。異常對象開始沿著調(diào)用棧向上冒泡。

第二步:DispatcherServlet 捕獲 (總指揮接管)

異常冒泡到了 Spring MVC 的最外層——DispatcherServlet.doDispatch() 方法。這里有一個巨大的 try-catch 塊(源碼簡化版):

// DispatcherServlet.java
try {
    // 嘗試執(zhí)行 Controller
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
} catch (Exception dispatchException) {
    // 【捕獲點(diǎn)】Controller 拋出的 OrderNotFoundException 在這里被捕獲!
    // 進(jìn)入異常處理流程
    processDispatchResult(processedRequest, response, mappedHandler, dispatchException, mv);
}

第三步:尋找解析器 (HandlerExceptionResolver)

processDispatchResult 內(nèi)部,Spring 會遍歷所有注冊的異常解析器鏈,問:“誰能處理 OrderNotFoundException?”

Spring Boot 默認(rèn)配置了 ExceptionHandlerExceptionResolver,它舉手說:“我能!我在 GlobalExceptionHandler 類里看到了一個 @ExceptionHandler 注解匹配這個異常。”

第四步:反射調(diào)用 (執(zhí)行救援邏輯)

ExceptionHandlerExceptionResolver 通過 Java 反射機(jī)制,調(diào)用我們寫的 handleOrderException(e) 方法。

  • 輸入:剛才捕獲的異常對象 e。
  • 執(zhí)行:運(yùn)行我們的代碼 return Result.fail(404, ...)
  • 輸出:拿到一個 Result 對象。

第五步:處理返回值 (HandlerMethodReturnValueHandler)

框架拿到 Result 對象后,并不會直接發(fā)給前端。它發(fā)現(xiàn)異常處理類上標(biāo)記了 @RestControllerAdvice (含 @ResponseBody)。
于是,它將任務(wù)移交給 RequestResponseBodyMethodProcessor。

  • 這個組件既負(fù)責(zé)處理 @RequestBody (讀),也負(fù)責(zé)處理 @ResponseBody (寫)。

第六步:內(nèi)容協(xié)商 (Content Negotiation)

RequestResponseBodyMethodProcessor 開始決定用什么格式返回數(shù)據(jù)。

  1. 查看數(shù)據(jù):返回值是 Result 對象。
  2. 查看需求:檢查 HTTP 請求頭 Accept。
    • 如果是瀏覽器默認(rèn)請求,通常包含 */*
    • 如果是 Postman/Ajax,可能是 application/json
    • 如果是舊系統(tǒng)調(diào)用,可能是 application/xml。
  3. 匹配轉(zhuǎn)換器:遍歷 HttpMessageConverter 列表。
    • Jackson 說:“我是處理 JSON 的,我可以把 Result 對象轉(zhuǎn)成 JSON 字符串。”

第七步:序列化與寫入 (Write Response)

Jackson 轉(zhuǎn)換器開始工作:

  1. Result 對象序列化為 JSON 字符串:{"code":404, "msg":"訂單ID不能為負(fù)數(shù)", "data":null}
  2. 獲取 HttpServletResponse 輸出流。
  3. 設(shè)置 Content-Type: application/json。
  4. 將字符串寫入流中,發(fā)送給客戶端。

3. 總結(jié)圖

[Controller 拋出異常] 
       ?
[JVM 冒泡]
       ?
[DispatcherServlet 捕獲 (catch)]
       ?
[尋找異常解析器 (Resolver)]
       ?
[反射調(diào)用 @ExceptionHandler 方法] --> 生成 Result 對象
       ?
[檢測 @ResponseBody 注解]
       ?
[內(nèi)容協(xié)商 (檢查 Accept 頭)]
       ?
[匹配 HttpMessageConverter (如 Jackson)]
       ?
[序列化 (Result -> JSON/XML)]
       ?
[寫入 HttpServletResponse]
       ?
[前端收到報錯]

深度解密:異常處理中的“內(nèi)容協(xié)商”

很多開發(fā)者認(rèn)為內(nèi)容協(xié)商(Content Negotiation)只在正常的 Controller 請求中生效,其實(shí)不然。異常處理返回的結(jié)果,同樣完美支持內(nèi)容協(xié)商。

1. 原理分析

無論是 Controller 的正常返回,還是 @ExceptionHandler 的異常返回,只要涉及 “對象轉(zhuǎn) HTTP Body”,Spring MVC 底層都交給同一個處理器:RequestResponseBodyMethodProcessor。

它會執(zhí)行標(biāo)準(zhǔn)的“談判”流程:

  1. 看貨:拿到返回值對象(Result)。
  2. 看客戶需求:讀取 HTTP 請求頭中的 Accept 字段(例如 application/jsonapplication/xml)。
  3. 找翻譯官:遍歷容器中所有的 HttpMessageConverter。
  4. 執(zhí)行轉(zhuǎn)換:找到能同時匹配“對象類型”和“客戶需求”的轉(zhuǎn)換器,執(zhí)行序列化。

2. 場景演示

假設(shè)我們引入了 jackson-dataformat-xml 依賴,Spring Boot 會自動注冊 XML 轉(zhuǎn)換器。

  • 場景 A:前端是 Vue/React (默認(rèn))
  • 請求頭:Accept: application/json
  • 響應(yīng):
{ "code": 500, "msg": "系統(tǒng)繁忙", "data": null }
  • 場景 B:前端是舊系統(tǒng) (指定 XML)
  • 請求頭:Accept: application/xml
  • 響應(yīng):
<Result>
    <code>500</code>
    <msg>系統(tǒng)繁忙</msg>
    <data/>
</Result>

結(jié)論:我們不需要修改一行 Java 代碼,異常信息就能自動適應(yīng)前端需要的格式。

Spring MVC vs Spring Boot:內(nèi)容協(xié)商誰在干活?

在這個過程中,我們需要理清兩者的分工:

  • Spring MVC(機(jī)制提供者)
    • 提供了 DispatcherServlet 捕獲異常的機(jī)制。
    • 提供了 @ControllerAdvice@ExceptionHandler 注解。
    • 提供了內(nèi)容協(xié)商管理器 (ContentNegotiationManager) 和消息轉(zhuǎn)換器接口 (HttpMessageConverter)。
  • 它是“發(fā)動機(jī)”。
  • Spring Boot(自動化配置)
    • 自動配置了 ErrorMvcAutoConfiguration(提供兜底的 /error 路徑)。
    • 自動識別 Classpath 下的 Jackson 包,并注冊了 JSON 和 XML 的轉(zhuǎn)換器。
  • 它是“裝配工”,讓你開箱即用。

SpringBoot的默認(rèn)異常處理方案

Spring Boot 的錯誤處理方案,核心就是一個詞:“自動兜底”

它的官方學(xué)名叫做 “默認(rèn)全局錯誤處理機(jī)制”。即使你一行異常處理代碼都不寫,Spring Boot 也能保證你的應(yīng)用在報錯時,不會直接把服務(wù)器炸了,或者給用戶看一堆亂碼,而是返回一個“雖然丑但結(jié)構(gòu)清晰”的錯誤響應(yīng)。

這個方案的核心由 1 個 Controller、2 種響應(yīng)模式1 個頁面 組成。

1. 核心組件:BasicErrorController

這是 Spring Boot 自動配置 (ErrorMvcAutoConfiguration) 幫你創(chuàng)建的一個特殊的 Controller。

  • 它的地位:和你的 OrderControllerUserController 平級,都是處理 HTTP 請求的。
  • 它的地盤:默認(rèn)監(jiān)聽 /error 路徑。
  • 工作原理
    1. 當(dāng)應(yīng)用中發(fā)生異常(且沒被 Spring MVC 攔截),或者訪問了不存在的路徑(404)。
    2. Servlet 容器(Tomcat)會捕捉到錯誤。
    3. Tomcat 發(fā)現(xiàn)你沒有配置專門的錯誤頁,于是根據(jù) Spring Boot 的約定,把請求轉(zhuǎn)發(fā) (Forward) 到 /error 路徑。
    4. BasicErrorController 收到請求,開始干活。

2. 智能響應(yīng):看人下菜碟(內(nèi)容協(xié)商)

BasicErrorController 非常智能,它會根據(jù)**“誰在訪問”**(檢查 HTTP 請求頭 Accept),決定返回什么格式的數(shù)據(jù)。它內(nèi)部定義了兩個處理方法:

模式 A:瀏覽器訪問 (返回 HTML)

  • 判斷依據(jù):請求頭包含 text/html。
  • 對應(yīng)方法errorHtml()
  • 結(jié)果
    • 它會去查找有沒有定義好的錯誤頁面(比如 error/404.html)。
    • 如果沒找到,就返回那個著名的 “Whitelabel Error Page”(白標(biāo)錯誤頁)。
    • 樣子你肯定見過:白底黑字,寫著 “This application has no explicit mapping for /error…”

模式 B:客戶端訪問 (返回 JSON)

  • 判斷依據(jù):請求頭不包含 text/html(比如 Postman, Ajax, 安卓 App)。
  • 對應(yīng)方法error()
  • 結(jié)果:返回一個標(biāo)準(zhǔn)的 JSON 對象。
    {
        "timestamp": "2023-12-04T12:00:00.000+00:00",
        "status": 500,
        "error": "Internal Server Error",
        "message": "/ by zero",
        "path": "/api/demo"
    }

3. 數(shù)據(jù)來源:DefaultErrorAttributes

你可能會問:“返回的 JSON 里那些 timestamp, status, message 字段是從哪來的?”

這是由另一個組件 DefaultErrorAttributes 負(fù)責(zé)收集的。它會從 Request 中提取所有的錯誤信息,封裝成一個 Map 給 BasicErrorController 使用。

如果你想在這個默認(rèn)的 JSON 里增加一個字段(比如 version: "v1.0"),或者隱藏異常堆棧,你可以繼承這個類并重寫相關(guān)方法。

4. 如何自定義?(給兜底方案換個皮膚)

雖然 Spring Boot 有兜底,但那個“白標(biāo)頁面”太丑了,JSON 格式可能也不符合你們公司的規(guī)范。你可以通過以下方式定制:

方式一:自定義錯誤頁面(最常用)

你只需要在 src/main/resources/templates/static/ 下創(chuàng)建一個 error 文件夾,然后放入對應(yīng)狀態(tài)碼的 HTML 文件:

  • error/404.html:專門展示 404 錯誤。
  • error/500.html:專門展示 500 錯誤。
  • error/4xx.html:展示所有 4 開頭的錯誤。

Spring Boot 掃到這些文件,就會自動用它們替換掉那個丑陋的白頁。

方式二:完全替換兜底邏輯(高階)

如果你覺得 BasicErrorController 邏輯不夠用,你可以實(shí)現(xiàn) ErrorController 接口,重寫 /error 的映射邏輯。但這種情況很少見,因為通常我們用 Spring MVC 的 @ControllerAdvice 就夠了。

到此這篇關(guān)于從拋出異常到返回JSON/XML:SpringBoot 異常處理全鏈路深度解析的文章就介紹到這了,更多相關(guān)springboot異常處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java實(shí)現(xiàn)從數(shù)據(jù)庫導(dǎo)出大量數(shù)據(jù)記錄并保存到文件的方法

    Java實(shí)現(xiàn)從數(shù)據(jù)庫導(dǎo)出大量數(shù)據(jù)記錄并保存到文件的方法

    這篇文章主要介紹了Java實(shí)現(xiàn)從數(shù)據(jù)庫導(dǎo)出大量數(shù)據(jù)記錄并保存到文件的方法,涉及Java針對數(shù)據(jù)庫的讀取及文件寫入等操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-11-11
  • SpringBoot?AOP?Redis實(shí)現(xiàn)延時雙刪功能實(shí)戰(zhàn)

    SpringBoot?AOP?Redis實(shí)現(xiàn)延時雙刪功能實(shí)戰(zhàn)

    本文主要介紹了SpringBoot?AOP?Redis實(shí)現(xiàn)延時雙刪功能實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • 一起來看看springboot集成redis的使用注解

    一起來看看springboot集成redis的使用注解

    這篇文章主要為大家詳細(xì)介紹了springboot集成redis的使用注解,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • Spring?Cloud集成Nacos?Config動態(tài)刷新源碼剖析

    Spring?Cloud集成Nacos?Config動態(tài)刷新源碼剖析

    這篇文章主要為大家介紹了Spring?Cloud集成Nacos?Config動態(tài)刷新源碼剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Mybatis實(shí)現(xiàn)分頁查詢的詳細(xì)流程

    Mybatis實(shí)現(xiàn)分頁查詢的詳細(xì)流程

    這篇文章主要給大家介紹了關(guān)于Mybatis實(shí)現(xiàn)分頁查詢的詳細(xì)流程,MyBatis是支持普通SQL查詢,存儲過程和高級映射的優(yōu)秀持久層框架,需要的朋友可以參考下
    2023-08-08
  • 使用EasyExcel根據(jù)模板導(dǎo)出文件方式

    使用EasyExcel根據(jù)模板導(dǎo)出文件方式

    本文基于EasyExcel庫開發(fā)ExportUtil工具類,支持模板化導(dǎo)出、列表/離散數(shù)據(jù)填充、HTTP響應(yīng)自動配置及異常統(tǒng)一處理,實(shí)現(xiàn)結(jié)構(gòu)化報表生成與文件下載功能
    2025-03-03
  • java string類的常用方法詳細(xì)介紹

    java string類的常用方法詳細(xì)介紹

    在開發(fā)過程中經(jīng)常會使用到j(luò)ava string類的方法,本文將以此問題進(jìn)行詳細(xì)介紹
    2012-11-11
  • java多線程實(shí)現(xiàn)取款小程序

    java多線程實(shí)現(xiàn)取款小程序

    這篇文章主要為大家詳細(xì)介紹了java多線程實(shí)現(xiàn)取款小程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • Java?二維數(shù)組創(chuàng)建及使用方式

    Java?二維數(shù)組創(chuàng)建及使用方式

    這篇文章主要介紹了Java?二維數(shù)組創(chuàng)建及使用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • JDK源碼白話解讀之ThreadLocal篇

    JDK源碼白話解讀之ThreadLocal篇

    其實(shí)網(wǎng)上有很多關(guān)于ThreadLocal的文章了,有不少文章也已經(jīng)寫的非常好了。但是很多同學(xué)反應(yīng)還有一些部分沒有講解的十分清楚,還是有一定的疑惑沒有想的十分清楚
    2022-02-02

最新評論

阿拉尔市| 开远市| 自治县| 西平县| 郁南县| 长岭县| 周至县| 贡觉县| 乐陵市| 顺平县| 西充县| 尼勒克县| 顺昌县| 合阳县| 荣成市| 定州市| 山东| 嵊泗县| 洪湖市| 万载县| 韶山市| 荥经县| 仪陇县| 新化县| 弥勒县| 武宁县| 天镇县| 甘洛县| 梅河口市| 太谷县| 永德县| 嫩江县| 平阴县| 天水市| 内黄县| 腾冲县| 岢岚县| 漳州市| 田东县| 军事| 临汾市|