Spring Boot 全局异常处理策略设计(二):DispatcherServlet 与异常解析责任链源码解析

文章目录

  • [Spring Boot 全局异常处理策略设计(二):DispatcherServlet 与异常解析责任链源码解析](#Spring Boot 全局异常处理策略设计(二):DispatcherServlet 与异常解析责任链源码解析)
    • [1. 为什么一定要从 DispatcherServlet 讲起](#1. 为什么一定要从 DispatcherServlet 讲起)
    • [2. DispatcherServlet 在请求中的角色定位](#2. DispatcherServlet 在请求中的角色定位)
    • [3. doDispatch:异常真正被捕获的地方](#3. doDispatch:异常真正被捕获的地方)
      • [3.1 doDispatch 的整体结构(简化)](#3.1 doDispatch 的整体结构(简化))
      • [3.2 Throwable 为什么会被单独捕获?](#3.2 Throwable 为什么会被单独捕获?)
    • [4. processDispatchResult:异常处理的真正入口](#4. processDispatchResult:异常处理的真正入口)
    • [5. processHandlerException:责任链的起点](#5. processHandlerException:责任链的起点)
    • [6. HandlerExceptionResolver 责任链模型](#6. HandlerExceptionResolver 责任链模型)
      • [6.1 接口定义](#6.1 接口定义)
      • [6.2 默认的三个异常解析器](#6.2 默认的三个异常解析器)
    • [7. Resolver 链的执行顺序是如何确定的](#7. Resolver 链的执行顺序是如何确定的)
    • [8. 异常是如何被"吃掉"的?](#8. 异常是如何被“吃掉”的?)
    • [9. 如果所有 Resolver 都不处理会怎样?](#9. 如果所有 Resolver 都不处理会怎样?)
    • [10. 异常责任链流程图](#10. 异常责任链流程图)
    • [11. 为什么说这是一个"非常优雅的设计"](#11. 为什么说这是一个“非常优雅的设计”)
    • [12. 本篇关键结论](#12. 本篇关键结论)
    • 参考资料
    • 结束语

Spring Boot 全局异常处理策略设计(二):DispatcherServlet 与异常解析责任链源码解析

1. 为什么一定要从 DispatcherServlet 讲起

在第一篇中我们已经明确了一点:
异常不是在 Controller 里被"解决"的,而是在框架层被"接管"的。

在 Spring MVC 中,这个"接管者"只有一个入口:

DispatcherServlet

无论你使用的是:

  • @ExceptionHandler
  • @ControllerAdvice
  • @ResponseStatus
  • Spring Boot 默认的 /error

它们最终都必须经过 DispatcherServlet 的调度与分发。


2. DispatcherServlet 在请求中的角色定位

在一次完整的请求处理中,DispatcherServlet 的职责可以概括为四步:

  1. 查找 Handler
  2. 执行 Handler
  3. 处理返回值
  4. 处理异常

异常并不是一个"补丁逻辑",而是 DispatcherServlet 的标准流程之一。


3. doDispatch:异常真正被捕获的地方

DispatcherServlet 的核心方法是 doDispatch,异常处理的关键逻辑就在这里。

3.1 doDispatch 的整体结构(简化)

java 复制代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
    try {
        // 1. 查找 Handler
        // 2. 执行 Handler
    } catch (Exception ex) {
        dispatchException = ex;
    } catch (Throwable err) {
        dispatchException = new ServletException(err);
    }

    processDispatchResult(request, response, handler, dispatchException);
}

非常重要的一点:

DispatcherServlet 并不会在 catch 中直接处理异常,而是统一交给 processDispatchResult。


3.2 Throwable 为什么会被单独捕获?

java 复制代码
catch (Throwable err) {
    dispatchException = new ServletException(err);
}

这里体现了一个非常关键的设计思想:

  • 框架不允许 Throwable 直接向外传播
  • 所有异常最终都会被"标准化"为 Exception

这保证了后续异常解析链的统一性。


4. processDispatchResult:异常处理的真正入口

java 复制代码
private void processDispatchResult(
        HttpServletRequest request,
        HttpServletResponse response,
        HandlerExecutionChain mappedHandler,
        Exception exception) {

    if (exception != null) {
        mv = processHandlerException(request, response, handler, exception);
    }
}

只要 exception != null,就会进入异常处理流程。


5. processHandlerException:责任链的起点

java 复制代码
protected ModelAndView processHandlerException(
        HttpServletRequest request,
        HttpServletResponse response,
        Object handler,
        Exception ex) {

    for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
        ModelAndView mv = resolver.resolveException(request, response, handler, ex);
        if (mv != null) {
            return mv;
        }
    }
    throw ex;
}

这一段代码,是 Spring MVC 异常机制的灵魂。

从中可以明确看出三点:

  1. 异常处理是一个 Resolver 链
  2. 按顺序逐个尝试解析
  3. 谁先返回非 null,谁就"吃掉"异常

6. HandlerExceptionResolver 责任链模型

6.1 接口定义

java 复制代码
public interface HandlerExceptionResolver {
    ModelAndView resolveException(
        HttpServletRequest request,
        HttpServletResponse response,
        Object handler,
        Exception ex);
}

设计目的非常明确:

给异常一个"翻译成响应"的机会


6.2 默认的三个异常解析器

Spring MVC 默认注册了三个 Resolver:

Resolver 作用
ExceptionHandlerExceptionResolver 处理 @ExceptionHandler
ResponseStatusExceptionResolver 处理 @ResponseStatus
DefaultHandlerExceptionResolver 处理 Spring 内置异常

它们构成了一条有顺序、有分工、有兜底的异常责任链。


7. Resolver 链的执行顺序是如何确定的

Resolver 并不是写死的,而是通过初始化流程注入:

java 复制代码
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);

最终顺序为:

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

顺序的设计逻辑是:

  • 用户自定义优先
  • 注解语义其次
  • 框架兜底最后

8. 异常是如何被"吃掉"的?

当某个 Resolver 返回了非 null 的 ModelAndView:

java 复制代码
if (mv != null) {
    return mv;
}

意味着:

  • 异常被成功解析
  • 后续 Resolver 不再执行
  • DispatcherServlet 不会再抛出异常

这也是为什么:

一个异常只会被一个 Resolver 处理


9. 如果所有 Resolver 都不处理会怎样?

java 复制代码
throw ex;

结果是:

  • 异常继续向上抛
  • 对 Servlet 容器来说,这是一个未处理异常
  • 在 Spring Boot 中,通常会被 /error 接管(后续篇章重点)

10. 异常责任链流程图

throw Exception
未处理
未处理
未处理
Controller 执行
DispatcherServlet
processHandlerException
ExceptionHandlerExceptionResolver
ResponseStatusExceptionResolver
DefaultHandlerExceptionResolver
异常继续抛出

图1 Spring MVC 异常解析责任链流程图


11. 为什么说这是一个"非常优雅的设计"

从源码可以清楚看到:

  • 没有 if-else 地狱
  • 没有硬编码异常类型
  • 完全遵循 开闭原则

你可以:

  • 插入自定义 Resolver
  • 调整顺序
  • 替换默认行为

而 DispatcherServlet 不需要修改一行代码。


12. 本篇关键结论

到这一篇为止,我们已经明确:

  1. DispatcherServlet 是异常处理的唯一入口
  2. 异常处理不是一个方法,而是一条责任链
  3. @ExceptionHandler 只是其中一个 Resolver
  4. Spring MVC 把"异常 → 响应"的逻辑彻底解耦

但还有几个绕不开的问题:

  • @ExceptionHandler 是如何被扫描并匹配异常的?
  • @ControllerAdvice 为什么能全局生效?
  • ResponseBody 是如何写入响应的?
  • Spring Boot 为什么要额外引入 /error?

👉 这些问题,必须进入 Resolver 内部才能解释清楚。


参考资料


结束语

掘金点击访问Qiuner CSDN点击访问Qiuner GitHub点击访问Qiuner Gitee点击访问Qiuner

专栏 简介
📊 一图读懂系列 图文并茂,轻松理解复杂概念
📝 一文读懂系列 深入浅出,全面解析技术要点
🌟持续更新 保持学习,不断进步
🎯 人生经验 经验分享,共同成长

你好,我是Qiuner. 为帮助别人少走弯路而写博客

如果本篇文章帮到了你 不妨点个赞 吧~ 我会很高兴的 😄 (^ ~ ^) 。想看更多 那就点个关注吧 我会尽力带来有趣的内容 😎。

代码都在Github或Gitee上,如有需要可以去上面自行下载。记得给我点星星哦😍

如果你遇到了问题,自己没法解决,可以去我掘金评论区问。CSDN评论区和私信消息看不完 掘金消息少一点.

上一篇推荐 链接
Java程序员快又扎实的学习路线 点击该处自动跳转查看哦
一文读懂 AI 点击该处自动跳转查看哦
一文读懂 服务器 点击该处自动跳转查看哦
2024年创作回顾 点击该处自动跳转查看哦
一文读懂 ESLint配置 点击该处自动跳转查看哦
老鸟如何追求快捷操作电脑 点击该处自动跳转查看哦
未来会写什么文章? 预告链接
一文读懂 XX? 点击该处自动跳转查看哦
2025年终总结 点击该处自动跳转查看哦
一图读懂 XX? 点击该处自动跳转查看哦
相关推荐
IT_陈寒4 分钟前
Redis内存暴涨,没想到是AOF重写捣的鬼
前端·人工智能·后端
ikoala4 分钟前
让 Codex 自己查账:一个省 token 的小技巧
前端·后端·ai编程
这个昵称不容易1 小时前
SpringBoot「衣依」服装销售平台:144个Java文件180个接口,从挑衣服到收货评价整条链路都在
java·springboot·毕设
计算机毕设定制辅导-无忧学长2 小时前
《基于微信小程序的居家医疗陪护预约系统设计与实现》
java·spring boot·微信小程序·uniapp·居家医疗陪护预约系统
文丑颜不良啊3 小时前
LeetCode2221~2240题目解析
java·leetcode
秋饼3 小时前
Computer Use Agent Java 落地实战:感知-行动循环、执行器与安全围栏生产级实现
java·ai·技术分享·后端开发
wo_jack4 小时前
C++ 入门笔记(十六):第16课 vector 模拟实现十问十答 —— 从三指针到迭代器失效
c++·后端·面试·stl·vector
量化分析码农5 小时前
【Python量化波动率实战 02】波动率会传染:EWMA 指数加权给近期行情更大话语权
后端
文丑颜不良啊5 小时前
LeetCode2161~2180题目解析
java·leetcode
GISer_Jing6 小时前
如何学习Agent
后端·langchain