Spring Boot异常处理try-catch应该怎么使用?

 更新时间:2026年01月31日 09:02:47   作者:Qiuner  
这篇文章详细介绍了Web系统中异常处理的重要性,以及SpringMVC的异常处理机制,解释了异常在系统中的角色,讨论了Java异常模型的关键认知,包括Throwable体系结构和异常传播,强调了在Web系统中不应该到处使用try-catch,而是应该在边界层统一处理异常

1. 为什么异常值得单独写一整个系列

在多数业务代码中,异常往往被当作一种“不得不写的语法”:

try {
    service.process();
} catch (Exception e) {
    log.error("error", e);
}

但在真实的 Web 系统中,异常从来不是一个语法问题,而是一个系统失控时的兜底机制

一次 HTTP 请求,往往会经历如下链路:

  • Filter
  • Interceptor
  • Controller
  • 参数绑定与校验
  • Service
  • DAO
  • 事务
  • AOP 代理
  • JSON 序列化

异常可以在任何一个环节出现,而且一旦出现,就会“逆着调用栈”向上冒泡,最终由框架决定:

  • 是否回滚事务
  • 是否返回 500
  • 返回什么格式
  • 是否记录堆栈
  • 是否暴露错误信息给前端

因此,异常不是“边角料”,而是整个调用链的终点汇合处

2. Java 异常模型的关键认知(只讲对后面有用的)

2.1 Throwable 体系结构

Throwable
 ├── Error
 └── Exception
      ├── RuntimeException
      └── Checked Exception
  • Error:JVM 级错误,应用通常无能为力
  • Exception:应用级异常
  • RuntimeException:Spring 默认认为这是“不可恢复异常”
  • Checked Exception:需要显式声明和处理

Spring 事务为什么默认只对 RuntimeException 回滚?
这个问题在后面的事务异常章节会专门展开。

2.2 异常传播是“反向调用链”

正常调用是:

Controller → Service → DAO

异常传播是:

DAO 抛异常 → Service → Controller → 框架

谁最后接住异常,谁就拥有最终解释权。

3. 为什么 Web 系统不能到处 try-catch

3.1 try-catch 的三个常见问题

  1. 吞异常,导致问题被掩盖
  2. 重复代码,Controller 层异常处理泛滥
  3. 破坏事务回滚逻辑

例如:

@Transactional
public void createOrder() {
    try {
        saveOrder();
    } catch (Exception e) {
        log.error("error", e);
    }
}

这个代码看起来稳健,实际上事务已经无法回滚

3.2 异常必须“集中处理”

在 Web 架构中,有一个非常重要的设计原则:

异常应该在“边界层”统一处理,而不是在业务层消化。

Spring MVC 正是基于这个原则,设计了一整套异常处理机制。

4. Spring MVC 的异常处理总体设计思想

4.1 正常流程 vs 异常流程

正常流程:

请求 → Handler → 返回值 → 响应

异常流程:

请求 → Handler → 抛异常 → 异常解析 → 响应

Spring MVC 的核心设计点在于:

异常不是 if-else 分支,而是一条独立的处理链路。

4.2 异常处理在 DispatcherServlet 中的位置

DispatcherServlet 是整个 MVC 的“总控中枢”。

在其核心方法 doDispatch 中,异常被统一捕获:

try {
    // 查找 Handler 并执行
} catch (Exception ex) {
    dispatchException = ex;
}

这意味着:

  • Controller 不需要感知异常如何返回
  • 框架会统一接管异常

5. Spring MVC 的三种基础异常处理方式

5.1 直接抛出异常(推荐)

@GetMapping("/order")
public Order getOrder() {
    throw new IllegalArgumentException("参数错误");
}

异常会交给框架处理,而不是在 Controller 内部解决。

5.2 @ExceptionHandler:局部异常处理

@RestController
public class OrderController {

    @ExceptionHandler(IllegalArgumentException.class)
    public String handleIllegalArg(Exception e) {
        return e.getMessage();
    }
}

特点:

  • 只对当前 Controller 生效
  • 适合非常局部的异常场景

5.3 @ControllerAdvice:全局异常处理(重点)

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ErrorResponse handle(Exception e) {
        return new ErrorResponse("500", e.getMessage());
    }
}

这是 Spring Boot 项目中最常见的异常处理入口

6. @ControllerAdvice 的设计价值

6.1 为什么它是“全局异常处理”的核心

@ControllerAdvice 本质上解决了三个问题:

  1. 异常集中管理
  2. 返回格式统一
  3. 与业务逻辑解耦

6.2 多个 ControllerAdvice 的顺序问题

Spring 支持定义多个全局异常处理器:

@Order(1)
@RestControllerAdvice
class BizExceptionHandler {}

@Order(2)
@RestControllerAdvice
class SystemExceptionHandler {}

优先级越小,越先执行。

7. 异常处理的第一版架构形态

在“入门阶段”,一个相对合理的异常架构通常是:

Controller
   ↓
抛异常
   ↓
@ControllerAdvice
   ↓
统一错误响应

对应的返回结构示例:

{
  "code": "SYSTEM_ERROR",
  "message": "系统异常,请稍后再试"
}

8. 异常处理流程图(概览)

渲染错误: Mermaid 渲染失败: Parse error on line 5: ...Resolver] D --> E[@ExceptionHandler ----------------------^ Expecting 'AMP', 'COLON', 'PIPE', 'TESTSTR', 'DOWN', 'DEFAULT', 'NUM', 'COMMA', 'NODE_STRING', 'BRKT', 'MINUS', 'MULT', 'UNICODE_TEXT', got 'LINK_ID'

图1 Spring MVC 异常处理基本流程图

9. 本篇小结(从入门视角看异常)

到这里,我们只做了三件事:

  1. 纠正“异常只是 try-catch”的认知
  2. 明确异常是 Web 系统的统一出口
  3. 理解 Spring MVC 为什么要集中处理异常

但我们还没有回答几个关键问题:

  • 异常是如何一步步被解析的?
  • 为什么 @ExceptionHandler 能生效?
  • Spring Boot 的 /error 是干什么的?
  • 为什么有些异常进不了 ControllerAdvice?

👉 这些问题,都需要进入源码层面才能解释清楚。

到此这篇关于Spring Boot异常处理try-catch应该怎么使用?的文章就介绍到这了,更多相关Spring Boot异常处理try-catch内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

参考资料

  • Spring Framework Reference – Exception Handling
    https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-exceptionhandler.html
  • Spring MVC 源码(DispatcherServlet)
    https://github.com/spring-projects/spring-framework

相关文章

  • 使用mybatis的@Interceptor实现拦截sql的方法详解

    使用mybatis的@Interceptor实现拦截sql的方法详解

    拦截器是一种基于 AOP(面向切面编程)的技术,它可以在目标对象的方法执行前后插入自定义的逻辑,本文给大家介绍了使用mybatis的@Interceptor实现拦截sql的方法,需要的朋友可以参考下
    2024-03-03
  • SpringBoot中实现数据脱敏处理的方法详解

    SpringBoot中实现数据脱敏处理的方法详解

    项目开发中,在处理敏感信息时,数据脱敏是一项重要的安全措施,本文主要为大家介绍了如何在SpringBoot项目中进行数据脱敏处理,有需要的可以了解下
    2025-03-03
  • 解决Spring boot 嵌入的tomcat不启动问题

    解决Spring boot 嵌入的tomcat不启动问题

    这篇文章主要介绍了解决Spring boot 嵌入的tomcat不启动问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • 详解Java编程中JavaMail API的使用

    详解Java编程中JavaMail API的使用

    这篇文章主要介绍了详解Java编程中JavaMail API的使用,通过JavaMail可以实现丰富的邮件类相关功能,需要的朋友可以参考下
    2015-11-11
  • MyBatis 执行动态 SQL语句详解

    MyBatis 执行动态 SQL语句详解

    大家对mybatis执行任意sql语句都了解,那么MyBatis执行动态SQL语句呢?下面脚本之家小编给大家解答下mybatis执行动态sql语句的方法,非常不错,感兴趣的朋友参考下吧
    2016-08-08
  • 聊聊spring继承的问题

    聊聊spring继承的问题

    这篇文章主要介绍了spring继承的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • tk.mybatis通用插件updateByPrimaryKeySelective无法自动更新列的解决办法

    tk.mybatis通用插件updateByPrimaryKeySelective无法自动更新列的解决办法

    tk.mybatis是一个很好用的通用插件,本文主要介绍了tk.mybatis通用插件updateByPrimaryKeySelective无法自动更新列的解决办法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • Spring Cloud 如何保证微服务内安全

    Spring Cloud 如何保证微服务内安全

    这篇文章主要介绍了Spring Cloud 如何保证微服务内安全的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • 深入理解Java中没那么简单的单例模式

    深入理解Java中没那么简单的单例模式

    这篇文章主要给大家详细介绍了Java单例模式,关于Java中的单例模式并非看起来那么简单的,为什么要这么说呢?下面通过这篇文章来一起看看吧,有需要的朋友们可以参考借鉴。
    2017-01-01
  • spring boot集成p6spy的最佳实践

    spring boot集成p6spy的最佳实践

    这篇文章主要介绍了spring boot集成p6spy的最佳实践-p6spy-spring-boot-starter的项目案例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-02-02

最新评论