问题
- 什么是父子容器
- 为什么需要父子容器
- 父子容器如何使用
案例
分别有两个包,一个是model1,一个是model2
model1:
@Component
public class Service1 {
public void s1(){
System.out.println("model1 -> s1");
}
}
@Component
public class Service2 {
@Autowired
Service1 service1;
public void s2(){
service1.s1();
}
}
@ComponentScan
public class MainConfig1 {
}
model2:
@Component
public class Service1 {
public void s1(){
System.out.println("model2 -> s1");
}
}
@Component
public class Service3 {
//这个是model1包下的
@Autowired
Service2 service2;
//这个是model2包下的
@Autowired
Service1 service1;
public void s1(){
service2.s2();
}
public void s2(){
service1.s1();
}
}
@ComponentScan
public class MainConfig2 {
}
测试:
@Test
public void testContainer(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MainConfig1.class, MainConfig2.class);
context.refresh();
}
运行结果:
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'service1' for bean class [com.spring.container.model2.Service1] conflicts with existing, non-compatible bean definition of same name and class [com.spring.container.model1.Service1]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:349)
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:287)
at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:132)
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:296)
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:250)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:207)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:175)
... 29 more
因为在spring容器中,有一个bean service1了,导致名字冲突了。
如何解决
对module1中的Service1进行修改?这个估计是行不通的,module1是别人以jar的方式提供给我们的,源码我们是无法修改的。
而module2是我们自己的开发的,里面的东西我们可以随意调整,那么我们可以去修改一下module2中的Service1,可以修改一下类名,或者修改一下这个bean的名称,此时是可以解决问题的。
不过大家有没有想过一个问题:如果我们的模块中有很多类都出现了这种问题,此时我们一个个去重构,还是比较痛苦的,并且代码重构之后,还涉及到重新测试的问题,工作量也是蛮大的,这些都是风险。
而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();
父子容器的特点:
- 父容器和子容器是相互隔离的,他们内部可以存在名称相同的bean
- 子容器可以访问父容器中的bean,而父容器不能访问子容器中的bean
- 调用子容器的getBean方法获取bean的时候,会沿着当前容器开始向上面的容器进行查找,直到找到对应的bean为止
- 子容器中可以通过任何注入方式注入父容器中的bean,而父容器中是无法注入子容器中的bean,原因是第2点
使用父子容器解决上个案例的问题
@Test
public void testContainer1(){
//父容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MainConfig1.class);
context.refresh();
//子容器
AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext();
childContext.register(MainConfig2.class);
//指定父容器
childContext.setParent(context);
childContext.refresh();
//从子容器中获取bean
Service3 service3 = childContext.getBean("service3", Service3.class);
service3.s1();
service3.s2();
}
运行结果:
model1 -> s1
model2 -> s1
父子容器使用注意点
我们使用容器的过程中,经常会使用到的一些方法,这些方法通常会在下面的两个接口中
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 testContainer2() {
//创建父容器parentFactory
DefaultListableBeanFactory parentFactory = new DefaultListableBeanFactory();
//向父容器parentFactory注册一个bean[userName->"路人甲Java"]
parentFactory.registerBeanDefinition("userName",
BeanDefinitionBuilder.
genericBeanDefinition(String.class).
addConstructorArgValue("spring").
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
}
运行结果:
获取bean【userName】:spring
[address]
有没有方式解决ListableBeanFactory接口不支持层次查找的问题?
spring有个工具类可以解决:BeanFactoryUtils
这个类中提供了很多静态方法,有很多支持层次查找的方法,源码你们可以去细看一下,名称中包含有Ancestors的都是支持层次查找的。
在上面的测试方法中添加下面代码:
//层次查找所有符合类型的bean名称
String[] beanNamesForTypeIncludingAncestors = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(childFactory, String.class);
System.out.println(Arrays.asList(beanNamesForTypeIncludingAncestors));
Map<String, String> beansOfTypeIncludingAncestors = BeanFactoryUtils.beansOfTypeIncludingAncestors(childFactory, String.class);
System.out.println(Arrays.asList(beansOfTypeIncludingAncestors));
运行结果:
获取bean【userName】:spring
[address]
[address, userName]
10:32:19.621 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'address'
[{address=上海, userName=spring}]
总结
- 本文需掌握父子容器的用法,了解父子容器的特点:子容器可以访问父容器中bean,父容器无法访问子容器中的bean
- BeanFactory接口支持层次查找
- ListableBeanFactory接口不支持层次查找
- BeanFactoryUtils工具类中提供了一些非常实用的方法,比如支持bean层次查找的方法等等