您现在的位置是:亿华云 > 知识
Spring 中的父子容器是咋回事?
亿华云2025-10-04 02:52:01【知识】3人已围观
简介来源:江南一点雨相信有小伙伴也听说过,在 SSM 项目中,Spring 容器是父容器,SpringMVC 是子容器,子容器可以访问父容器的 Bean,但是父容器不能访问子容器的 Bean。更近一步,有
来源:江南一点雨
相信有小伙伴也听说过,父容在 SSM 项目中,器咋Spring 容器是回事父容器,SpringMVC 是父容子容器,子容器可以访问父容器的器咋 Bean,但是回事父容器不能访问子容器的 Bean。
更近一步,父容有小伙伴可能也了解过,器咋不用父子容器,回事单纯就用一个 SpringMVC 容器似乎也可以,父容项目也能运行。器咋
那么现在问题来了:既然单纯一个 SpringMVC 容器就能使项目跑起来,回事那我们为什么还要用父子容器?父容父子容器的优势是什么?
带着这个问题,今天松哥来和小伙伴们聊一聊父子容器。器咋
1. 父子容器
首先,回事其实父子这种设计很常见,松哥记得在之前的 Spring Security 的系列文章中,Spring Security 中的 AuthenticationManager 其实也是类似的设计,估计那里就是借鉴了 Spring 中的父子容器设计。
当使用了父子容器之后,源码下载如果去父容器中查找 Bean,那么就单纯的在父容器中查找 Bean;如果是去子容器中查找 Bean,那么就会先在子容器中查找,找到了就返回,没找到则继续去父容器中查找,直到找到为止(把父容器都找完了还是没有的话,那就只能抛异常出来了)。
2. 为什么需要父子容器
2.1 问题呈现
为什么需要父子容器?老老实实使用一个容器不行吗?
既然 Spring 容器中有父子容器,那么这个玩意就必然有其使用场景。
松哥举一个简单的例子。
假设我有一个多模块项目,其中有商家模块和客户模块,商家模块和客户模块中都有角色管理 RoleService,项目结构如下图:
├── admin
│ ├── pom.xml
│ └── src
│ ├── main
│ │ ├── java
│ │ └── resources
├── consumer
│ ├── pom.xml
│ └── src
│ ├── main
│ │ ├── java
│ │ │ └── org
│ │ │ └── javaboy
│ │ │ └── consumer
│ │ │ └── RoleService.java
│ │ └── resources
│ │ └── consumer_beans.xml
├── merchant
│ ├── pom.xml
│ └── src
│ ├── main
│ │ ├── java
│ │ │ └── org
│ │ │ └── javaboy
│ │ │ └── merchant
│ │ │ └── RoleService.java
│ │ └── resources
│ │ └── merchant_beans.xml
└── pom.xml
现在 consumer 和 merchant 中都有一个 RoleService 类,然后在各自的配置文件中,都将该类注册到 Spring 容器中。
org.javaboy.consumer.RoleService:
public class RoleService{
public String hello(){
return "hello consumer";
}
}
org.javaboy.merchant.RoleService:
public class RoleService{
public String hello(){
return "hello merchant";
}
}
consumer_beans.xml 如下:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="org.javaboy.consumer.RoleService" id="roleService"/></beans>merchant_beans.xml 如下:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="org.javaboy.merchant.RoleService" id="roleService"/></beans>大家注意,这两个 Bean 同名。
现在,在 admin 模块中,同时依赖 consumer 和 merchant,同时加载这两个配置文件,那么能不能同时向 Spring 容器中注册两个来自不同模块的同名 Bean 呢?
代码如下:
ClassPathXmlApplicationContext ctx = newClassPathXmlApplicationContext();
ctx.setConfigLocations("consumer_beans.xml", "merchant_beans.xml");
ctx.refresh();
org.javaboy.merchant.RoleService rs1 = ctx.getBean(org.javaboy.merchant.RoleService.class);
org.javaboy.consumer.RoleService rs2 = ctx.getBean(org.javaboy.consumer.RoleService.class);
这个执行之后会抛出如下问题:

小伙伴们看到,这个是找不到 org.javaboy.consumer.RoleService 服务,但是香港云服务器另外一个 RoleService 其实是找到了,因为默认情况下后面定义的同名 Bean 把前面的覆盖了,所以有一个 Bean 就找不到了。
如果不允许 Bean 的覆盖,那么可以进行如下配置:
ClassPathXmlApplicationContext ctx = newClassPathXmlApplicationContext();
ctx.setConfigLocations("consumer_beans.xml", "merchant_beans.xml");
ctx.setAllowBeanDefinitionOverriding(false);
ctx.refresh();
此时一启动就直接报错了:

意思也说的比较明确了,Bean 的定义冲突了,所以定义失败。
那么有没有办法能够优雅的解决上面这个问题呢?答案就是父子容器!
2.2 父子容器
对于上面的问题,我们可以将 consumer 和 merchant 配置成父子关系或者兄弟关系,就能很好的解决这个问题了。
2.2.1 兄弟关系先来看兄弟关系,代码如下:
ClassPathXmlApplicationContext ctx = newClassPathXmlApplicationContext();
ClassPathXmlApplicationContext child1 = new ClassPathXmlApplicationContext("consumer_beans.xml");
ClassPathXmlApplicationContext child2 = new ClassPathXmlApplicationContext("merchant_beans.xml");
child1.setParent(ctx);
child2.setParent(ctx);
ctx.setAllowBeanDefinitionOverriding(false);
ctx.refresh();
org.javaboy.consumer.RoleService rs1 = child1.getBean(org.javaboy.consumer.RoleService.class);
org.javaboy.merchant.RoleService rs2 = child2.getBean(org.javaboy.merchant.RoleService.class);
System.out.println("rs1.hello() = "+ rs1.hello());
System.out.println("rs2.hello() = "+ rs2.hello());
小伙伴们看一下,这种针对 consumer 和 merchant 分别创建了容器,这种容器关系就是兄弟容器,这两个兄弟有一个共同的 parent 就是 ctx,现在可以在各个容器中获取到自己的 Bean 了。
需要注意的是服务器租用,上面这种结构中,子容器可以获取到 parent 的 Bean,但是无法获取到兄弟容器的 Bean,即如果 consumer 中引用了 merchant 中的 Bean,那么上面这个配置就有问题了。
2.2.2 父子关系现在假设用 consumer 做 parent 容器,merchant 做 child 容器,那么配置如下:
ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("consumer_beans.xml");
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext("merchant_beans.xml");
child.setParent(parent);
child.refresh();
org.javaboy.consumer.RoleService rs1 = parent.getBean(org.javaboy.consumer.RoleService.class);
org.javaboy.merchant.RoleService rs2 = child.getBean(org.javaboy.merchant.RoleService.class);
org.javaboy.consumer.RoleService rs3 = child.getBean(org.javaboy.consumer.RoleService.class);
System.out.println("rs1.hello() = "+ rs1.hello());
System.out.println("rs2.hello() = "+ rs2.hello());
System.out.println("rs3.hello() = "+ rs3.hello());
首先创建两个容器,分别是 parent 和 child,然后为 child 容器设置 parent,设置完成后记得要刷新 child 容器。
现在我们就可以从 parent 容器中去获取 parent 容器中原本就存在的 Bean,也可以从 child 容器中去获取 child 容器原本的 Bean 或者是 parent 的 Bean 都可以。
这就是父子容器。
父容器和子容器本质上是相互隔离的两个不同的容器,所以允许同名的 Bean 存在。当子容器调用 getBean 方法去获取一个 Bean 的时候,如果当前容器没找到,就会去父容器查找,一直往上找,找到为止。
核心就是 BeanFactory,这个松哥之前文章已经和小伙伴们介绍过了(BeanFactoryPostProcessor 和 BeanPostProcessor 有什么区别?),BeanFactory 有一个子类 HierarchicalBeanFactory,看名字就是带有层级关系的 BeanFactory:
public interface HierarchicalBeanFactory extends BeanFactory{
/
** * Return the parent bean factory, or { @codenull} if there is none.
*/ @Nullable BeanFactory getParentBeanFactory();
/
*** Return whether the local bean factory contains a bean of the given name,
* ignoring beans defined in ancestor contexts.
* <p>This is an alternative to { @codecontainsBean}, ignoring a bean
* of the given name from an ancestor bean factory.
* @paramname the name of the bean to query
* @returnwhether a bean with the given name is defined in the local factory
* @seeBeanFactory#containsBean
*/ boolean containsLocalBean(String name);
}
只要是 HierarchicalBeanFactory 的子类就能配置父子关系。父子关系图如下:

2.3 特殊情况
需要注意的是,并不是所有的获取 Bean 的方法都支持父子关系查找,有的方法只能在当前容器中查找,并不会去父容器中查找:
ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("consumer_beans.xml");
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext("merchant_beans.xml");
child.setParent(parent);
child.refresh();
String[] names1 = child.getBeanNamesForType(org.javaboy.merchant.RoleService.class);
String[] names2 = child.getBeanNamesForType(org.javaboy.consumer.RoleService.class);
System.out.println("names1 = "+ Arrays.toString(names1));
System.out.println("names2 = "+ Arrays.toString(names2));
如上,根据类型去查找 Bean 名称的时候,我们所用的是 getBeanNamesForType 方法,这个方法是由 ListableBeanFactory 接口提供的,而该接口和 HierarchicalBeanFactory 接口并无继承关系,所以 getBeanNamesForType 方法并不支持去父容器中查找 Bean,它只在当前容器中查找 Bean。
但是!如果你确实有需求,希望能够根据类型查找 Bean 名称,并且还能够自动去父容器中查找,那么可以使用 Spring 给我们提供的工具类,如下:
ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("consumer_beans.xml");
ClassPathXmlApplicationContext child = newClassPathXmlApplicationContext();
child.setParent(parent);
child.refresh();
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(child, org.javaboy.consumer.RoleService.class);
for(String name : names) {
System.out.println("name = "+ name);
}
不过这个查找,对于父子容器中同名的 Bean 是查找不出来名字的。
2.4 Spring 和 SpringMVC
上面的内容理解了,Spring 和 SpringMVC 之间的关系就好理解了,Spring 是父容器,SpringMVC 则是子容器。
在 SpringMVC 中,初始化 DispatcherServlet 的时候,会创建出 SpringMVC 容器,并且为 SpringMVC 容器设置 parent,相关代码如下:
FrameworkServlet#initWebApplicationContext:
protected WebApplicationContext initWebApplicationContext(){
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it wac = this.webApplicationContext;
if (wac instanceofConfigurableWebApplicationContext cwac && !cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set // the root application context (if any; may be null) as the parentcwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one // has been registered in the servlet context. If one exists, it is assumed // that the parent context (if any) has already been set and that the // user has performed any initialization such as setting the context idwac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local onewac = createWebApplicationContext(rootContext);
}
returnwac;
}
这里的 rootContext 就是父容器,wac 就是子容器,无论哪种方式得到的子容器,都会尝试给其设置一个父容器。
如果我们在一个 Web 项目中,不单独配置 Spring 容器,直接配置 SpringMVC 容器,然后将所有的 Bean 全部都扫描到 SpringMVC 容器中,这样做是没有问题的,项目是可以正常运行的。但是一般项目中我们还是会把这两个容器分开,分开有如下几个好处:
方便管理,SpringMVC 主要处理控制层相关的 Bean,如 Controller、视图解析器、参数处理器等等,而 Spring 层则主要控制业务层相关的 Bean,如 Service、Mapper、数据源、事务、权限等等相关的 Bean。对于新手而言,两个容器分开配置,可以更好的理解 Controller、Service 以及 Dao 层的关系,也可以避免写出来在 Service 层注入 Controller 这种荒唐代码。另外再额外说一句,有的小伙伴可能会问,如果全部 Bean 都扫描到 Spring 容器中不用 SpringMVC 容器行不行?这其实也可以!但是需要一些额外的配置,这个松哥下篇文章再来和小伙伴们细述。
3. 小结
好啦,Spring 容器中的父子容器现在大家应该明白了吧?可以给非 ListableBeanFactory 容器设置父容器,父容器不可以访问子容器的 Bean,但是子容器可以访问父容器的 Bean。
很赞哦!(3)
相关文章
- tk域名是什么域名?新手对tk域名有什么看法?
- 通过构建一个简单的掷骰子游戏去学习怎么用Python编程
- 一个90后女孩的第一个 Spring Boot 应用
- 并发编程之抽象队列同步器AQS应用ReentrantLock
- 顶级域名可以增加企业品牌的价值。随着经济的快速发展,域名已不再是企业在网络中的独立地位。顶级域名的服务范围、企业产品、综合形象体现等,对于企业单位来说,顶级域名的重要性不言而喻。
- 华宇应邀出席2020中国移动全球合作伙伴大会
- Webpack教程:如何从头开始设置 webpack 5
- SpringCloud 整合Seata 解决分布式事务(搭建+源码)
- 四、长串数字域名
- Python3.10第二个alpha版本!最新特性值得关注
热门文章
站长推荐
互联网其实拼的也是人脉,域名投资也是一个时效性很强的东西,一个不起眼的消息就会引起整个域名投资市场的动荡,因此拓宽自己的人脉圈,完善自己的信息获取渠道,让自己能够掌握更为多样化的信息,这样才更有助于自己的域名投资。
使用Antd表格组件实现日程表
在多线程环境中,Synchronized到底该不该用?
码农欢乐多:属于程序员的爆笑时刻
域后缀首选.com,.net,然后是.cn。后缀选择不当,导致流量损失。域名是企业与互联网网址之间的链接,关键是企业在网络上存在的标志。因此,选择好域名是开展网上工作的首要重要条件。
第二十二届高交会今日开幕!提亚 “可视化管控大屏”现场直击!
缩减SCSS 50%样式代码的14条实战经验
使用CQRS避免查询对模型设计的影响