Spring(学习笔记)基础篇 -父子容器与延迟加载!

重点标识

一般父子容器会用在多模块项目中,Spring MVC项目就是一个典型的父子容器,Spring作为父容器存在,Spring MVC则作为子容器存在,例如,你可以在Controller中注入Service,但是不能在Service中注入Controller,这就是因为Controlelr是Spring MVC提供的,它是子容器注入的Bean。

父子容器有点类似于Java中的继承关系一样,子类继承父类,可以调用父类中的方法,但是父类不能反过来调用子类独有的方法。

父子容器同样也是这样,子容器可以获取父容器注入的Bean,而父容器不能获取子容器注入的bean。

代码演示

首先,我们准备一个简单的多模块项目,这里我准备一个总的项目,然后在里面建立父模块,子模块,以及demo模块进行验证父子容器。

在这里插入图片描述

接下来,我们在parent和child项目中分别引入Spring依赖,然后随便建一个类,注入到Spring容器中,这里比较简单,所以我只展示parent项目中的代码,child项目实际上是一样的。

     <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.1.4</version>
        </dependency>

public class UserService {
}

 <bean class="org.tongzhou.service.UserService" id="userService"/>

紧接着,在demo项目中,引入parent和child的依赖。

		<dependency>
            <groupId>org.tongzhou</groupId>
            <artifactId>parent</artifactId>
            <version>1.0-SNAPSHOT</version>
            </dependency>
        <dependency>
            <groupId>org.tongzhou</groupId>
            <artifactId>child</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

问题案例

先看一个有问题的案例,在demo项目中,加载parent和child项目的配置文件,使用ConfigLocations方法同时加载,启动容器,虽然不会报错,但是后面配置文件的的容器加载会自动覆盖掉前面的加载,也就是说,只有后面的容器加载才会成功,在这个例子中,就是只能获取到child项目中注入的Bean。

public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
        context.setConfigLocations("parent_bean.xml","child_bean.xml");
        //容器真正的初始化是从refresh开始的
        context.refresh();
      //  UserService bean = context.getBean(UserService.class);
        org.tongzhou.service.UserService bean1 = context.getBean(org.tongzhou.service.UserService.class);
      //  System.out.println(bean);
        System.out.println(bean1);
    }
当然,这也是有解决办法的,使用setAllowBeanDefinitionOverriding设置为false,不进行覆盖,但这样又会导致一个问题,如果出现同名bean,就会在启动时候报错。
context.setAllowBeanDefinitionOverriding(false);

父子容器

接下来,我们来看重点,父子容器,其中一个容器作为父容器,另一个容器作为子容器存在,从子容器中就可以get到所有的Bean,这里需要注意的是,子容器必须进行初始化refresh。

public static void main(String[] args) {
        ClassPathXmlApplicationContext child_context = new ClassPathXmlApplicationContext("child_bean.xml");

        ClassPathXmlApplicationContext parent_context = new ClassPathXmlApplicationContext("parent_bean.xml");

        child_context.setParent(parent_context);
        //容器真正的初始化是从refresh开始的
        child_context.refresh();
      //  UserService bean = context.getBean(UserService.class);
        org.tongzhou.service.UserService bean1 = child_context.getBean(org.tongzhou.service.UserService.class);
        org.tongzhou.parent.service.UserService bean = child_context.getBean(org.tongzhou.parent.service.UserService.class);
        System.out.println(bean);
        System.out.println(bean1);
    }

这里我们可以简单看下getbean里面的源码,加深一下理解

进入getbean可以看到这个方法

 public <T> T getBean(Class<T> requiredType) throws BeansException {
        this.assertBeanFactoryActive();
        return this.getBeanFactory().getBean(requiredType);
    }
  

然后进入到genBean中,我们可以看到默认使用的就是DefaultListableBeanFactory这个类
在这里插入图片描述
找到里面的解析方法resolveBean,我们可以看到,他一开始就是从容器中寻找要得到的bean,如果能找得到,则直接返回,如果找不到,也就是namedBean 不存在等于null,则往下面getParentBeanFactory获取父容器,看看父容器有没有,然后再resolveBean解析。

private <T> T resolveBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) {
        NamedBeanHolder<T> namedBean = this.resolveNamedBean(requiredType, args, nonUniqueAsNull);
        if (namedBean != null) {
            return namedBean.getBeanInstance();
        } else {
            BeanFactory parent = this.getParentBeanFactory();
            if (parent instanceof DefaultListableBeanFactory) {
                DefaultListableBeanFactory dlfb = (DefaultListableBeanFactory)parent;
                return dlfb.resolveBean(requiredType, args, nonUniqueAsNull);
            } else if (parent != null) {
                ObjectProvider<T> parentProvider = parent.getBeanProvider(requiredType);
                if (args != null) {
                    return parentProvider.getObject(args);
                } else {
                    return nonUniqueAsNull ? parentProvider.getIfUnique() : parentProvider.getIfAvailable();
                }
            } else {
                return null;
            }
        }
    }
就是这个样子,其实,大部分情况下,Spring都是支持子容器去父容器找的,但是也有一些特殊的方法,不支持去父容器查找,如下。
String[] beanNamesForType = context.getBeanNamesForType(UserService.class);

这个方法,我们可以从两方面进行理解,第一就是String数组中,如果重名,实际上也没法放到数据中,所以这种方法获取同名的Bean,没什么意义,第二就是从Spring容器的结构上看。Bean的本质上就是通过BeanFactory进行一系列的操作的,我们通过getbean方法进去到BeanFactory中,就可以看到如下:
在这里插入图片描述
HierarchicalBeanFactory 顾名思义,层级Bean工厂,点进去就可以看到两个方法,getParentBeanFactory,就是获取父类,containsLocalBean这个则是判断当前容器有没有这个bean。

public interface HierarchicalBeanFactory extends BeanFactory {
    @Nullable
    BeanFactory getParentBeanFactory();

    boolean containsLocalBean(String name);
}

我们再往深去看,就可以看到Spring默认使用的DefaultListableBeanFactory也在里面,属于是一脉相承了。
在这里插入图片描述
那为什么getBeanNamesForType不行呢,我们可以看到这个方法是在ListableBeanFactory里面,和上面那个同层级,这样一看就很清楚了,从Spring的这个结构上,他就是不支持的。
在这里插入图片描述

兄弟容器

还有一种兄弟容器的存在,一般用的比较少,了解一下。
兄弟容器有一个共同的父类,但是他们之间是相互独立的,互相之间不能访问彼此的Bean.

   ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
        ClassPathXmlApplicationContext child_context = new ClassPathXmlApplicationContext("child_bean.xml");
        ClassPathXmlApplicationContext parent_context = new ClassPathXmlApplicationContext("parent_bean.xml");
        child_context.setParent(context);
        parent_context.setParent(context);
        context.refresh();

延迟加载

最后,再补充一下,我们知道,Spring中加载配置文件,就会加载bean,进行注入,但是有些情况下,可能需要延迟加载,我们可以这样配置lazy-init=“true”,就ok了。

 <bean class="org.tongzhou.parent.service.UserService" id="userService" lazy-init="true"/>
  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值