SpringMVC 父子容器是什么知道吗?
- 标签:后端、Spring
🏛 业务架构师
SpringMVC 的父子容器指的是:根容器(Root ApplicationContext,由 ContextLoaderListener 创建)管 Service/DAO 等业务层 Bean,子容器(Servlet ApplicationContext,由 DispatcherServlet 创建)管 Controller、ViewResolver 等 Web 层 Bean。子容器可以访问父容器的 Bean,反之不行——单向依赖,Web 层依赖业务层。
业务架构师视角,我讲两个层面:
分层治理的意图:父容器承载与 Web 无关的业务能力,理论上可被多种接入端复用(Servlet、定时任务、MQ 监听器各自持有子容器,共享同一个根容器)。这本质是**"业务核心与接入渠道分离"**的架构思想在容器层的体现。
现实趋势:Spring Boot 之后这套父子容器基本退场——SpringBootApplication 只创建一个容器,ComponentScan 一次扫完。原因很务实:父子容器带来的经典坑(Service 上 @Transactional 正常、Controller 层 AOP 不生效,因为两个容器各自的配置和代理策略不一致)造成的排查成本,远大于理论上的复用收益。
所以我面试时的标准回答是:讲清机制(历史项目仍在用 XML + WAR 部署时要会维护),然后表明观点——现代业务系统用 Spring Boot 单容器 + 分包/分模块(Maven 多模块)实现分层,边界靠工程结构和依赖检查工具(ArchUnit)约束,而不是靠容器物理隔离。理解历史设计的取舍,比照搬历史设计更重要。
🏗 系统架构师
结构:传统 war 部署的 Spring MVC 有两个容器——Root WebApplicationContext(service/dao 层,ContextLoaderListener 创建)与 Servlet WebApplicationContext(Controller、视图,DispatcherServlet 创建),后者以前者为 parent:子可见父的 Bean,反之不可见。
为什么这样设计:
- 层隔离:Web 层只能消费业务层,不能反向被依赖,强制单向依赖;
- 多 DispatcherServlet 共享:多个 Servlet(REST 与页面并存)共享同一套业务 Bean,不重复装配。
现实演进:Spring Boot 默认单一容器——内嵌容器时代双容器的原始动机消失了,简化反而消除了大量心智负担(getRootWebApplicationContext 找不到上下文的经典坑随之消失)。
它留给我的架构遗产是「容器分层隔离」思想:做平台化产品(插件体系、租户定制)时,用父子 ApplicationContext 隔离平台内核与插件——插件之间 Bean 互不可见,只通过父容器暴露的 API 协作,能救回很多灾难性耦合。
💻 全栈开发专家
传统 Spring MVC(web.xml 时代)有两个容器:
- Root WebApplicationContext:
ContextLoaderListener创建,装 Service、DAO、事务、数据源——业务层容器 - Servlet WebApplicationContext:每个 DispatcherServlet 一个,装 Controller、视图解析器——Web 层容器,parent 指向 Root
子容器找不到 Bean 会向上委托父容器,反之不行(父不知道子)。设计目的是隔离:可以有多个 DispatcherServlet 各带一套 Controller,共用一套业务层。
真实踩过的坑:事务切面配在了 Servlet 容器的扫描范围、Service 却在 Root 容器,@Transactional 失效;或者两个容器重复扫描同一个包,Bean 创建两遍、单例失效。排查方法:分别打印两个容器的 getBeanDefinitionNames() 对比交集。
Spring Boot 里这个问题消失了:Boot 只启动一个 ApplicationContext,官方明确不再用父子容器,ContextLoaderListener 那套被内嵌容器 + 自动配置取代。所以答完原理我一定补一句"Boot 是单容器",显示真的对比过两者——这也是面试官想听的分水岭。