重点标识
一般父子容器会用在多模块项目中,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"/>