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

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

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

前言

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

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

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

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

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

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

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

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

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

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

這是企業(yè)級(jí)開發(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 編寫全局異常處理器 (救援隊(duì))

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

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

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

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

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

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

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

// 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 會(huì)遍歷所有注冊(cè)的異常解析器鏈,問:“誰能處理 OrderNotFoundException?”

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

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

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

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

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

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

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

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

RequestResponseBodyMethodProcessor 開始決定用什么格式返回?cái)?shù)據(jù)。

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

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

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

  1. Result 對(duì)象序列化為 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 對(duì)象
       ?
[檢測(cè) @ResponseBody 注解]
       ?
[內(nèi)容協(xié)商 (檢查 Accept 頭)]
       ?
[匹配 HttpMessageConverter (如 Jackson)]
       ?
[序列化 (Result -> JSON/XML)]
       ?
[寫入 HttpServletResponse]
       ?
[前端收到報(bào)錯(cuò)]

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

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

1. 原理分析

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

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

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

2. 場(chǎng)景演示

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

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

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

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

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

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

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

Spring Boot 的錯(cuò)誤處理方案,核心就是一個(gè)詞:“自動(dòng)兜底”。

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

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

1. 核心組件:BasicErrorController

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

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

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

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

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

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

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

  • 判斷依據(jù):請(qǐng)求頭不包含 text/html(比如 Postman, Ajax, 安卓 App)。
  • 對(duì)應(yīng)方法error()
  • 結(jié)果:返回一個(gè)標(biāo)準(zhǔn)的 JSON 對(duì)象。
    {
        "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

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

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

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

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

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

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

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

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

Spring Boot 掃到這些文件,就會(huì)自動(dòng)用它們替換掉那個(gè)丑陋的白頁(yè)。

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

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

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

相關(guān)文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    JDK源碼白話解讀之ThreadLocal篇

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

最新評(píng)論

双城市| 五家渠市| 合阳县| 宁强县| 黑龙江省| 江孜县| 五华县| 永丰县| 咸阳市| 永康市| 平江县| 任丘市| 榆社县| 高阳县| 宁武县| 介休市| 平安县| 朔州市| 民乐县| 正宁县| 策勒县| 尉犁县| 长白| 洞口县| 建始县| 江陵县| 威海市| 郯城县| 比如县| 恩施市| 乌鲁木齐县| 香港| 嘉义市| 高青县| 卓尼县| 鄂伦春自治旗| 邓州市| 肥乡县| 莱州市| 英德市| 太和县|