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? 点击该处自动跳转查看哦
相关推荐
程序员欣宸14 小时前
LangChain4j实战之十一:结构化输出之二,function call
java·ai·langchain4j
howeres14 小时前
基于 Spring Boot 的插件化 JAR 包热加载方案
spring boot·jar
七夜zippoe14 小时前
Spring Data JPA原理与实战 Repository接口的魔法揭秘
java·ffmpeg·事务·jpa·repository
memgLIFE14 小时前
SQL 优化方法详解(2)
java·数据库·sql
一条咸鱼_SaltyFish14 小时前
[Day7] contract-ai深度剖析:大模型适配项目的架构设计与策略实现
java·开发语言·人工智能·经验分享·程序人生·开源软件·个人开发
毕设源码-赖学姐14 小时前
【开题答辩全过程】以 高校人才就业管理系统为例,包含答辩的问题和答案
java
万邦科技Lafite14 小时前
淘宝商品SKU规格信息获取指南及item_skuAPI开放接口详解
java·大数据·数据库·mysql·电商开放平台·淘宝开放平台
C雨后彩虹14 小时前
亲子游戏问题
java·数据结构·算法·华为·面试
heartbeat..14 小时前
Java 持久层框架 MyBatis 全面详解(附带Idea添加对应的XML文件模板教程)
java·数据库·intellij-idea·mybatis·持久化
BD_Marathon14 小时前
Spring——AOP工作流程
java·后端·spring