Spring系列第24篇:父子容器详解

面试官:如果只用一个容器可以么,所有的配置都交给一个spring容器加载?

我:应该不行吧!

面试官:确定不行么?

我:让我想一会。。。。。我感觉是可以的,也可以正常运行。

面试官:那我们又回到了开头的问题,为什么要用父子容器呢?

我:我叫你哥好么,别这么玩我了,被你绕晕了?

面试官:好吧,你回去试试看吧,下次再来告诉我,出门右转,不送!

我:脸色变绿了,灰头土脸的走了。

回去之后,我好好研究了一番,下次准备再去给面试官一点颜色看看。

主要的问题


  1. 什么是父子容器?

  2. 为什么需要用父子容器?

  3. 父子容器如何使用?

下面我们就来探讨探讨。

我们先来看一个案例


系统中有2个模块:module1和module2,两个模块是独立开发的,module2会使用到module1中的一些类,module1会将自己打包为jar提供给module2使用,我们来看一下这2个模块的代码。

模块1

放在module1包中,有3个类

Service1

package com.javacode2018.lesson002.demo17.module1;

import org.springframework.stereotype.Component;

@Component

public class Service1 {

public String m1() {

return “我是module1中的Servce1中的m1方法”;

}

}

Service2

package com.javacode2018.lesson002.demo17.module1;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

@Component

public class Service2 {

@Autowired

private com.javacode2018.lesson002.demo17.module1.Service1 service1; //@1

public String m1() { //@2

return this.service1.m1();

}

}

上面2个类,都标注了@Compontent注解,会被spring注册到容器中。

@1:Service2中需要用到Service1,标注了@Autowired注解,会通过spring容器注入进来

@2:Service2中有个m1方法,内部会调用service的m1方法。

来个spring配置类:Module1Config

package com.javacode2018.lesson002.demo17.module1;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan

public class Module1Config {

}

上面使用了@CompontentScan注解,会自动扫描当前类所在的包中的所有类,将标注有@Compontent注解的类注册到spring容器,即Service1和Service2会被注册到spring容器。

再来看模块2

放在module2包中,也是有3个类,和模块1中的有点类似。

Service1

模块2中也定义了一个Service1,内部提供了一个m2方法,如下:

package com.javacode2018.lesson002.demo17.module2;

import org.springframework.stereotype.Component;

@Component

public class Service1 {

public String m2() {

return “我是module2中的Servce1中的m2方法”;

}

}

Service3

package com.javacode2018.lesson002.demo17.module2;

import com.javacode2018.lesson002.demo17.module1.Service2;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

@Component

public class Service3 {

//使用模块2中的Service1

@Autowired

private com.javacode2018.lesson002.demo17.module2.Service1 service1; //@1

//使用模块1中的Service2

@Autowired

private com.javacode2018.lesson002.demo17.module1.Service2 service2; //@2

public String m1() {

return this.service2.m1();

}

public String m2() {

return this.service1.m2();

}

}

@1:使用module2中的Service1

@2:使用module1中的Service2

先来思考一个问题

上面的这些类使用spring来操作会不会有问题?会有什么问题?

这个问题还是比较简单的,大部分人都可以看出来,会报错,因为两个模块中都有Service1,被注册到spring容器的时候,bean名称会冲突,导致注册失败。

来个测试类,看一下效果

package com.javacode2018.lesson002.demo17;

import com.javacode2018.lesson001.demo21.Config;

import com.javacode2018.lesson002.demo17.module1.Module1Config;

import com.javacode2018.lesson002.demo17.module2.Module2Config;

import org.junit.Test;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ParentFactoryTest {

@Test

public void test1() {

//定义容器

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

//注册bean

context.register(Module1Config.class, Module2Config.class); //@1

//启动容器

context.refresh();

}

}

@1:将Module1Config、Module2Config注册到容器,spring内部会自动解析这两个类上面的注解,即:@CompontentScan注解,然后会进行包扫描,将标注了@Compontent的类注册到spring容器。

运行test1输出

下面是部分输出:

Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name ‘service1’ for bean class [com.javacode2018.lesson002.demo17.module2.Service1] conflicts with existing, non-compatible bean definition of same name and class [com.javacode2018.lesson002.demo17.module1.Service1]

service1这个bean的名称冲突了。

那么我们如何解决?

对module1中的Service1进行修改?这个估计是行不通的,module1是别人以jar的方式提供给我们的,源码我们是无法修改的。

而module2是我们自己的开发的,里面的东西我们可以随意调整,那么我们可以去修改一下module2中的Service1,可以修改一下类名,或者修改一下这个bean的名称,此时是可以解决问题的。

不过大家有没有想过一个问题:如果我们的模块中有很多类都出现了这种问题,此时我们一个个去重构,还是比较痛苦的,并且代码重构之后,还涉及到重新测试的问题,工作量也是蛮大的,这些都是风险。

而spring中的父子容器就可以很好的解决上面这种问题。

什么是父子容器


创建spring容器的时候,可以给当前容器指定一个父容器。

BeanFactory的方式

//创建父容器parentFactory

DefaultListableBeanFactory parentFactory = new DefaultListableBeanFactory();

//创建一个子容器childFactory

DefaultListableBeanFactory childFactory = new DefaultListableBeanFactory();

//调用setParentBeanFactory指定父容器

childFactory.setParentBeanFactory(parentFactory);

ApplicationContext的方式

//创建父容器

AnnotationConfigApplicationContext parentContext = new AnnotationConfigApplicationContext();

//启动父容器

parentContext.refresh();

//创建子容器

AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext();

//给子容器设置父容器

childContext.setParent(parentContext);

//启动子容器

childContext.refresh();

上面代码还是比较简单的,大家都可以看懂。

我们需要了解父子容器的特点,这些是比较关键的,如下。

父子容器特点

  1. 父容器和子容器是相互隔离的,他们内部可以存在名称相同的bean

  2. 子容器可以访问父容器中的bean,而父容器不能访问子容器中的bean

  3. 调用子容器的getBean方法获取bean的时候,会沿着当前容器开始向上面的容器进行查找,直到找到对应的bean为止

  4. 子容器中可以通过任何注入方式注入父容器中的bean,而父容器中是无法注入子容器中的bean,原因是第2点

使用父子容器解决开头的问题


关键代码

@Test

public void test2() {

//创建父容器

AnnotationConfigApplicationContext parentContext = new AnnotationConfigApplicationContext();

//向父容器中注册Module1Config配置类

parentContext.register(Module1Config.class);

//启动父容器

parentContext.refresh();

//创建子容器

AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext();

//向子容器中注册Module2Config配置类

childContext.register(Module2Config.class);

//给子容器设置父容器

childContext.setParent(parentContext);

//启动子容器

childContext.refresh();

//从子容器中获取Service3

Service3 service3 = childContext.getBean(Service3.class);

System.out.println(service3.m1());

System.out.println(service3.m2());

}

运行输出

我是module1中的Servce1中的m1方法

我是module2中的Servce1中的m2方法

这次正常了。

父子容器使用注意点


我们使用容器的过程中,经常会使用到的一些方法,这些方法通常会在下面的两个接口中

org.springframework.beans.factory.BeanFactory

org.springframework.beans.factory.ListableBeanFactory

这两个接口中有很多方法,这里就不列出来了,大家可以去看一下源码,这里要说的是使用父子容器的时候,有些需要注意的地方。

BeanFactory接口,是spring容器的顶层接口,这个接口中的方法是支持容器嵌套结构查找的,比如我们常用的getBean方法,就是这个接口中定义的,调用getBean方法的时候,会从沿着当前容器向上查找,直到找到满足条件的bean为止。

而ListableBeanFactory这个接口中的方法是不支持容器嵌套结构查找的,比如下面这个方法

String[] getBeanNamesForType(@Nullable Class<?> type)

获取指定类型的所有bean名称,调用这个方法的时候只会返回当前容器中符合条件的bean,而不会去递归查找其父容器中的bean。

来看一下案例代码,感受一下:

@Test

public void test3() {

//创建父容器parentFactory

DefaultListableBeanFactory parentFactory = new DefaultListableBeanFactory();

//向父容器parentFactory注册一个bean[userName->“路人甲Java”]

parentFactory.registerBeanDefinition(“userName”,

BeanDefinitionBuilder.

genericBeanDefinition(String.class).

addConstructorArgValue(“路人甲Java”).

getBeanDefinition());

//创建一个子容器childFactory

DefaultListableBeanFactory childFactory = new DefaultListableBeanFactory();

//调用setParentBeanFactory指定父容器

childFactory.setParentBeanFactory(parentFactory);

//向子容器parentFactory注册一个bean[address->“上海”]

childFactory.registerBeanDefinition(“address”,

BeanDefinitionBuilder.

genericBeanDefinition(String.class).

addConstructorArgValue(“上海”).

getBeanDefinition());

System.out.println(“获取bean【userName】:” + childFactory.getBean(“userName”));//@1

System.out.println(Arrays.asList(childFactory.getBeanNamesForType(String.class))); //@2

}

上面定义了2个容器

父容器:parentFactory,内部定义了一个String类型的bean:userName->路人甲Java

子容器:childFactory,内部也定义了一个String类型的bean:address->上海

@1:调用子容器的getBean方法,获取名称为userName的bean,userName这个bean是在父容器中定义的,而getBean方法是BeanFactory接口中定义的,支持容器层次查找,所以getBean是可以找到userName这个bean的

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

面试题总结

其它面试题(springboot、mybatis、并发、java中高级面试总结等)

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
的负担。**[外链图片转存中…(img-5zo87bB0-1713338748977)]

[外链图片转存中…(img-rK85r1Fu-1713338748978)]

[外链图片转存中…(img-OhF2Skb1-1713338748978)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

面试题总结

其它面试题(springboot、mybatis、并发、java中高级面试总结等)

[外链图片转存中…(img-6A5drW9N-1713338748979)]

[外链图片转存中…(img-NcBRYmMn-1713338748979)]

[外链图片转存中…(img-73j9Yuf0-1713338748979)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值