16.spring系列- 父子容器

问题

  1. 什么是父子容器
  2. 为什么需要父子容器
  3. 父子容器如何使用

案例

分别有两个包,一个是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();

父子容器的特点:

  1. 父容器和子容器是相互隔离的,他们内部可以存在名称相同的bean
  2. 子容器可以访问父容器中的bean,而父容器不能访问子容器中的bean
  3. 调用子容器的getBean方法获取bean的时候,会沿着当前容器开始向上面的容器进行查找,直到找到对应的bean为止
  4. 子容器中可以通过任何注入方式注入父容器中的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}]

总结

  1. 本文需掌握父子容器的用法,了解父子容器的特点:子容器可以访问父容器中bean,父容器无法访问子容器中的bean
  2. BeanFactory接口支持层次查找
  3. ListableBeanFactory接口不支持层次查找
  4. BeanFactoryUtils工具类中提供了一些非常实用的方法,比如支持bean层次查找的方法等等
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值