SpringMVC 异常处理?Spring 父子容器?

深入剖析 SpringMVC 异常处理与 Spring 父子容器

在 Spring + SpringMVC 的 Web 应用中,有两个核心概念经常被开发者提起:异常处理机制父子容器关系。理解它们不仅有助于写出更健壮的代码,还能避免许多隐藏的配置陷阱。本文将从源码逻辑到实际应用,结合流程图与结构图,为你系统梳理这两块知识。


一、SpringMVC 异常处理机制

1.1 核心流程:谁来解决我的异常?

当 SpringMVC 的 DispatcherServlet 在处理请求过程中抛出异常时,它并不会直接把异常抛给容器,而是委托给一组 异常处理器(HandlerExceptionResolver)。流程如下:

不能
全部不能处理
Controller 抛出异常
遍历所有

HandlerExceptionResolver
当前Resolver

能否处理?
调用 resolver.resolveException()
返回 ModelAndView 或错误响应
继续下一个Resolver
继续向上抛出异常

最终由容器处理

SpringMVC 默认注册了三个异常解析器,它们按固定顺序执行,一旦某个解析器返回非空的 ModelAndView,后续解析器就不再执行。

1.2 三大默认异常解析器

解析器 作用 触发条件
ExceptionHandlerExceptionResolver 处理被 @ExceptionHandler 注解标记的方法 控制器类或 @ControllerAdvice 中定义了匹配的异常处理方法
ResponseStatusExceptionResolver 处理带有 @ResponseStatus 注解的异常类 抛出的异常类上标注了 @ResponseStatus
DefaultHandlerExceptionResolver 处理 SpringMVC 内置的标准异常 TypeMismatchExceptionMissingServletRequestParameterException
1.2.1 ExceptionHandlerExceptionResolver

这是最常用、最灵活的解析器。它会在 @Controller@ControllerAdvice 类中查找带有 @ExceptionHandler 注解的方法,根据异常类型进行匹配。

java 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ArithmeticException.class)
    public Result handleArithmetic(ArithmeticException e) {
        return Result.error("除数不能为零");
    }
}
1.2.2 ResponseStatusExceptionResolver

如果自定义异常类上标注了 @ResponseStatus,当该异常被抛出时,解析器会自动设置 HTTP 状态码。

java 复制代码
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
}
1.2.3 DefaultHandlerExceptionResolver

处理 SpringMVC 自身抛出的标准异常,比如参数类型不匹配、缺少必要参数等。它会返回一个默认的错误视图(如 400 错误页面)。

1.3 异常处理整体时序图

ExceptionResolvers Controller HandlerAdapter DispatcherServlet Client ExceptionResolvers Controller HandlerAdapter DispatcherServlet Client alt [能处理] loop [每个解析器] 请求 执行 Controller 调用方法 抛出异常 传播异常 遍历解析器 能否处理? 解析异常 → ModelAndView 返回错误响应

💡 注意 :如果所有解析器都返回 null,则异常会继续上抛,最终导致 500 错误或由 Servlet 容器处理。

1.4 实战建议

  • 使用 @ControllerAdvice 统一处理业务异常,避免在每个 Controller 中重复写 try-catch。
  • 自定义异常时,可结合 @ResponseStatus 快速指定状态码。
  • 若需记录日志,可在全局异常处理方法中添加日志输出。

二、Spring 父子容器(Spring + SpringMVC)

在传统的 SSM(Spring + SpringMVC + MyBatis)项目中,应用启动时会创建两个 Spring 容器:

  • 父容器 (Root WebApplicationContext):由 ContextLoaderListener 启动,加载 applicationContext.xml 或通过 @Configuration 配置的 Spring 核心组件(如 Service、DAO、数据源、事务管理等)。
  • 子容器 (Servlet WebApplicationContext):由 DispatcherServlet 启动,加载 spring-mvc.xml 或 MVC 配置(如 Controller、视图解析器、拦截器等)。

2.1 父子容器结构图

子容器 SpringMVC
父容器 Spring
父容器无法访问子容器
子容器可以访问父容器
Service Bean
DAO Bean
数据源
事务管理器
Controller Bean
拦截器
视图解析器

2.2 核心区别与规则

特性 父容器(Spring) 子容器(SpringMVC)
加载时机 应用启动时由 ContextLoaderListener 加载 DispatcherServlet 初始化时加载
配置位置 applicationContext.xml@Configuration spring-mvc.xml@Configuration + @EnableWebMvc
管理的 Bean Service、DAO、数据源、事务、AOP 等 Controller、拦截器、视图解析器、局部异常处理器
能否访问对方 ❌ 不能访问子容器的 Bean ✅ 可以访问父容器的 Bean
Properties 隔离 各自加载的 *.properties 文件互不共享 子容器无法直接使用父容器的属性文件(除非通过 Bean 引用)

2.3 为什么这样设计?

  • 职责分离:父容器负责业务逻辑和数据层,子容器只负责 Web 层。子容器依赖父容器,但父容器不应感知 Web 层,避免循环依赖。
  • 模块化 :可以在同一个父容器下挂载多个子容器(例如多个 DispatcherServlet 对应不同模块)。
  • 性能优化:Controller 的创建和销毁由子容器管理,父容器不需要扫描 Web 层的注解。

2.4 常见问题与注意事项

❌ 问题1:父容器中扫描了 @Controller

如果父容器配置了 <context:component-scan base-package="com.example" />(未排除 @Controller),会导致两个问题:

  • Controller 被父容器和子容器各初始化一次,可能出现事务代理失效。
  • @ResponseBody 等 Web 相关注解可能无法正常工作。

✅ 正确做法

父容器配置扫描时排除 @Controller

xml 复制代码
<context:component-scan base-package="com.example">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

子容器只扫描 @Controller

xml 复制代码
<context:component-scan base-package="com.example" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
❌ 问题2:在父容器 Bean 中试图 @Autowired 子容器的 Bean

例如在 Service 中注入 Controller ------ 启动时会报错,因为父容器找不到对应类型的 Bean。

✅ 解决方案

重新审视设计,Service 不应该依赖 Controller。如果确实需要(极少见),可以通过 ApplicationContext 手动获取子容器的 Bean,但强烈不推荐。

❌ 问题3:属性文件隔离

父容器加载的 jdbc.properties 在子容器中无法通过 @Value 直接注入。如果需要共享,可以在父容器中配置 PropertySourcesPlaceholderConfigurer,子容器通过引用父容器的 Bean 间接使用。

2.5 现代 Spring Boot 环境下的变化

在 Spring Boot 中,默认只有一个容器 (基于 SpringApplication.run 创建)。传统的父子容器分离不再必须,但如果你手动创建 SpringApplicationBuilder,仍然可以实现父子容器。Spring Boot 更推荐单一容器 + 明确的分层包结构。


三、总结

知识点 关键结论
SpringMVC 异常处理 按顺序遍历 HandlerExceptionResolver,优先使用 @ExceptionHandler,其次是 @ResponseStatus,最后处理标准异常。建议使用 @ControllerAdvice 统一管理。
父子容器 父容器(Spring)不能访问子容器(SpringMVC)的 Bean;子容器可以访问父容器的 Bean。属性文件相互隔离。传统 SSM 项目必须正确配置扫描包,避免重复加载。

理解这两个机制,可以让你在排查 Web 应用异常、处理依赖注入失败、设计分层架构时更加游刃有余。

如果你正在使用 Spring Boot,绝大多数默认配置已经替你做好了,但掌握原理仍然有助于应对复杂场景下的定制需求。

📌 参考资料

相关推荐
鬼先生_sir2 小时前
Spring AI Alibaba 用户使用手册
java·人工智能·springai
有梦想的小何2 小时前
从0到1搭建可靠消息链路:RocketMQ重试 + Redis幂等实战
java·redis·bootstrap·rocketmq
大数据新鸟2 小时前
HashMap、Hashtable、ConcurrentHashMap 核心对比
java
MX_93592 小时前
Spring MVC拦截器
java·后端·spring·mvc
橘子编程2 小时前
MindOS:你的AI第二大脑知识库
java·开发语言·人工智能·计算机网络·ai
XWalnut2 小时前
LeetCode刷题 day9
java·算法·leetcode
忧郁的Mr.Li2 小时前
JAVA工具类---PDF电子签章工具类
java·pdf
零二年的冬2 小时前
epoll详解
java·linux·开发语言·c++·链表
凭君语未可2 小时前
Java 中的接口是什么
java·开发语言