上篇文章已经对 BeanDefinition 做了一系列的介绍,这篇文章讲一些 BeanDefinition 合并的一些知识,完善整个 BeanDefinition 的体系,Spring 在创建一个 bean 时多次进行了 BeanDefinition 的合并。本文主要对应官网中 1.7 小节
上篇文章主要说了 BeanDefinition 的一些属性,其中有以下几个属性:
org.springframework.beans.factory.config.BeanDefinition
这两个属性和 BeanDefinition 的合并相关,那么先考虑一个问题,什么是合并?
什么是合并?
先看官网上的一段介绍:
大概翻译如下:
一个 BeanDefinition 包含了很多的配置信息,包括构造参数,setter 方法的参数还有容器特定的一些配置信息,比如初始化方法,静态工厂方法等等。一个子 BeanDefinition 可以从它的父 BeanDefinition 继承配置信息,不仅如此,还可以覆盖其中的一些值或者添加一些自己需要的属性。使用 BeanDefinition 的父子定义可以减少很多的重复属性的设置。父 BeanDefinition 可以作为 BeanDefinition 定义的模板。
我们通过一个例子来观察下合并发生了什么,编写一个 Demo 如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="parent" abstract="true"
class="com.wxx.official.merge.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="child"
class="com.wxx.official.merge.DerivedTestBean"
parent="parent" >
<property name="name" value="override"/>
</bean>
</beans>
public class DerivedTestBean {
private String name;
private String age;
public void setName(String name) {
this.name = name;
}
public void setAge(String age) {
this.age = age;
}
public String getName() {
return name;
}
public String getAge() {
return age;
}
}
public class TestBean {
private String name;
private String age;
public void setName(String name) {
this.name = name;
}
public void setAge(String age) {
this.age = age;
}
public String getName() {
return name;
}
public String getAge() {
return age;
}
}
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("application.xml");
DerivedTestBean derivedTestBean = (DerivedTestBean) cc.getBean("child");
System.out.println("derivedTestBean 的 name = " + derivedTestBean.getName());
System.out.println("derivedTestBean 的 age = " + derivedTestBean.getAge());
}
}
运行结果:
derivedTestBean 的 name = override
derivedTestBean 的 age = 1
在上面例子中,我们将 DerivedTestBean 的 parent 属性设置为了 parent,指向了我们的 TestBean,同时将 TestBen 的 age 属性设置为 1,但是我们在配置文件中并没有直接设置 DerivedTestBean 的 age 属性。但是最后的运行结果却发现 DerivedTestBean 中的 age 属性有值了,并且为 1。就是我们在其 parent bean(也就是 TestBean)中设置的值。也就是说,子 BeanDefinition 会从父 BeanDefinition 中继承没有的属性。另外,DerivedTestBean 和 TestBean 都指定了 name 属性,但是可以发现,这个值并没有被覆盖掉,也就是说,子 BeanDefinition 中已经存在的属性不会被父 BeanDefinition 所覆盖
合并的总结:
- 子 BeanDefinition 会从父 BeanDefinition 中继承没有的属性
- 这个过程中,子 BeanDefinition 中已经存在的属性不会被父 BeanDefinition 所覆盖
关于合并需要注意的点:
- 子 BeanDefinition 中 class 属性如果为 null,同时父 BeanDefinition 又指定了 class 属性,那么子 BeanDefinition 也会继承这个 class 属性。
- 子 BeanDefinition 必须要兼容父 BeanDefinition 中所有的属性,这是什么意思呢?以我们上面的 demo 为例,我们在父 BeanDefinition 中指定了 name 和 age 属性,但是如果子 BeanDefinition 中提供了一个 name 的 setter 方法,这个时候 Spring 会在启动的时候报错。因为子 BeanDefinition 不能继承所有来自父 BeanDefinition 的属性。
- 关于 BeanDefinition 中 abstract 属性的说明:
a. 并不是作为父 BeanDefinition 就一定要设置 abstract 属性为 true,abstract 只代表了这个 BeanDefinition 是否要被 Spring 进行实例化并被创建对应的 bean,如果为 true,代表容器不需要去对其进行实例化。
b. 如果一个 BeanDefinition 被当作父 BeanDefinition 使用,并且没有指定其 class 属性,那么必须要设置其 abstract 为 true。
c. abstract=true 一般会和父 BeanDefinition 一起使用,因为当我们设置某个 BeanDefinition 的 abstract=true 时,一般都是要将其当作 BeanDefinition 的模板来使用,否则这个 BeanDefinition 也没有意义。除非我们使用其他 BeanDefinition 来继承它的属性
Spring 在哪些阶段做了合并?
- 扫描并获取到 BeanDefinition:
这个阶段的操作主要发生在 invokeBeanFactoryPostProcessors,对应的执行该方法的类为:PostProcessorRegistrationDelegate,对应的调用栈如下:
方法源码如下:
org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessorsxxx
省略部分代码,这部分代码和 BeanFactoryPostProcessor 接口相关,和这篇的 BeanDefinition 的合并无关,下片容器的扩展点再介绍
画图如下:
即每次调用 beanFactory.getBeanNamesForType 都进行了一次 BeanDefinition 的合并,getBeanNamesForType 这个方法主要目的是为了指定类型 BeanDefinition 的名称,之后通过 BeanDefinition 的名称找到指定的 BeanDefinition,然后获取对应的 bean,比如上面方法三次获取的都是 BeanDefinitionRegistryPostProcessor 这个类型 BeanDefinition。
现在思考一个问题,为什么这一步需要合并???
- 实例化
Spring 在实例化一个对象也会进行 BeanDefinition 的合并。
第一次:
org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
第二次:
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
我们可以发现这两次合并有一个共同的特点,就是合并之后立马利用了合并之后的 BeanDefinition,简称为 mbd,然后做了一系列的判断,比如上的 dependsOn != null && mbd.isSingleton()。基于上面几个例子我们来分析:为什么要合并???
为什么要合并?
在扫描阶段,之所以发生了合并,是因为 Spring 需要拿到指定的实现了 BeanDefinitionRegistryPostProcessor 接口 BeanDefinition 的名称,也就是说,Spring 需要用到 BeanDefinition 的名称,才进行了一次 BeanDefinition 的合并。在实例化阶段,是因为 Spring 需要用到 BeanDefinition 中一系列属性做判断所有才进行了一次合并。总结起来就是一个原因:Spring 需要用到 BeanDefinition 的属性,要保证获取到的 BeanDefinition 属性是正确的。
那么问题来了,为什么获取到的 BeanDefinition 属性可能是不正确的呢?
主要两个原因:
- 作为子 BeanDefinition,属性本身就可能有缺失,比如开头的例子,子 BeanDefinition 没有 age 属性,age 属性在父 BeanDefinition 中。
- Spring 提供了很多的扩展点,在启动容器的时候,可能会修改 BeanDefinition 中的属性。比如一个正常实现了 BeandFactoryPostProcessor 就能修改容器中任意的 BeanDefinition 属性。在后面的容器的扩展点再说。
合并的代码分析:
因为合并的代码其实很简单,这里一并分析了,也可以加深对合并的理解:
org.springframework.beans.factory.support.AbstractBeanFactory#getMergedLocalBeanDefinition
org.springframework.beans.factory.support.AbstractBeanFactory#getMergedBeanDefinition
上图的这段代码整体不难理解,可能发生的疑惑主要是两个点:
1. pbd = getMergedBeanDefinition(parentBeanName);
这里进行的是父 BeanDefinition 合并,是方法的递归调用,这是因为在合并的时候父 BeanDefinition 可能还不是一个合并后的 BeanDefinition
2. containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()
如果加了下面的嵌套配置,那么 containingBd 就不会为 null:
<bean id="indexService" class="com.wxx.official.merge.IndexService" scope="prototype">
<property name="lookUpService">
<bean class="com.wxx.official.merge.LookUpService" scope="singleton"></bean>
</property>
</bean>
在这个例子中,containingBd 是 IndexService,此时 IndexService 是一个原型的 BeanDefinition,但 LookUpService 是一个单例的 BeanDefinition,这个时候经过合并,LookUpService 也会变成一个原型的 BeanDefinition。
总结:
这篇文章我觉得重要的是,要明白 Spring 为什么合并,之所以每次都需要用到 BeanDefinition 进行一次合并,是为了每次拿的是最新的,最有效的 BeanDefinition,因为利用容器提供了一些扩展点我们可以修改 BeanDefinition 中的属性。关于容器的扩展点,比如上文提到的 BeanFactoryPostProcessor 以及 BeanDefinitionRegistryPostProcessor,在后面的文章再写了。
最后一句话,BeanDefinition 是整个 Spring 的基石,我本人也是在花费大量时间研究相关知识,加油,共勉!!
标签:name,Spring,age,合并,String,源码,BeanDefinition,属性 From: https://blog.51cto.com/u_15668812/7454784