文章目录
1. 什么是BeanDefinitionRegistry?
BeanDefinitionRegistry
是一个非常重要的接口,存在于
Spring
的
org.springframework.beans.factory.support
包中,它是
Spring
中注册和管理
BeanDefinition
的核心组件。
让我们回顾一下上一篇说的
BeanDefinition
。在
Spring
中,一个
Bean
就是一个被
Spring
管理的对象,而一个
BeanDefinition
则是一个
Bean
的配置描述,它描述了一个
Bean
的数据。它包含了
Bean
的类名、是否为抽象类、构造函数和属性值等信息。这些元数据将指导
Spring
如何创建和初始化
Bean
。
再来看一下
BeanDefinitionRegistry
的作用,
BeanDefinitionRegistry
的主要职责就是注册和管理这些
BeanDefinition
。我们可以把它看作是一个存放
BeanDefinition
的注册表,向其中注册新的
BeanDefinition
,或者检索和删除现有的
BeanDefinition
。它提供了一些方法,如
registerBeanDefinition(String, BeanDefinition)
,
removeBeanDefinition(String)
,和
getBeanDefinition(String)
,用于执行这些操作。
在
Spring
的内部,
BeanDefinitionRegistry
通常由
BeanFactory
实现,特别是
DefaultListableBeanFactory
和
GenericApplicationContext
,它们都实现了这个接口。
2. 为什么需要BeanDefinitionRegistry?
如果
BeanDefinitionRegistry
不存在,
Spring
的某些核心功能会受到什么样的影响?
- 资源解析的统一性:
BeanDefinition
作为一个统一的数据结构存储了Bean
的配置信息。如果没有BeanDefinitionRegistry
,每种配置方式(XML
、注解、Java
配置)都需要各自的专门数据结构。这不仅会导致资源解析的代码复杂度增加,还可能在不同的解析机制之间产生不一致性。 - 依赖查找和注入:
BeanDefinitionRegistry
提供了一个中心位置,充当了Bean
定义的中央存储,可以快速查找Bean
的定义。如果没有它,当需要注入一个Bean
的依赖时,Spring
不仅需要遍历所有的配置源来查找对应的Bean
,而且还可能遭遇Bean
定义不一致的问题,这会显著降低性能和准确性。
Bean
定义不一致的例子如下:
<!-- in config1.xml --><beanid="sampleBean"class="com.example.SampleBean1"/><!-- in config2.xml --><beanid="sampleBean"class="com.example.SampleBean2"/>
这里,
sampleBean
在两个配置文件中都有定义,但它们引用了不同的类。如果没有
BeanDefinitionRegistry
集中处理这些定义,那么
Spring
在尝试初始化
sampleBean
时可能会遭遇混淆,比如
Spring
尝试创建
ServiceA
的实例并为它注入
sampleBean
时,就会出现一个问题:
Spring
应该选择哪一个
sampleBean
的定义?
com.example.SampleBean1
还是
com.example.SampleBean2
?
这就是所谓的“
Bean
定义不一致”问题。如果
Spring
不知道哪一个定义是正确的,那么它可能会注入错误的
Bean
,从而导致应用程序的行为出现问题或者失败。这也可能导致应用在运行时出现不可预测的错误,因为注入的
Bean
并不是应用期望的版本或类型。
通过使用
BeanDefinitionRegistry
,
Spring
可以在应用程序启动时检测这类问题,并在
Bean
定义冲突或不一致时提供明确的错误消息,而不是在运行时遭遇不确定的行为或错误。
- 延迟初始化和作用域管理:
BeanDefinitionRegistry
存储了Bean
的作用域和其他元数据。如果没有这个BeanDefinitionRegistry
,Spring
在执行Bean
的延迟加载或根据作用域创建Bean
时,需要重新解析原始的配置资源,这增加了处理时间并可能导致潜在的配置错误。 - 配置验证:当所有
BeanDefinition
注册到BeanDefinitionRegistry
后,Spring
可以进行配置的校验,例如检查循环依赖、确保Bean
定义的完整性等。如果没有BeanDefinitionRegistry
,Spring
需要在每次Bean
初始化时进行检查,这不仅导致性能下降,还可能漏掉某些隐晦的配置问题。 - 生命周期管理:没有
BeanDefinitionRegistry
存储生命周期回调、初始化方法等信息,Spring
在管理Bean
的生命周期时,需要从原始的配置源获取这些信息。这不仅增加了管理的复杂度,还会使生命周期回调会变得复杂和笨重。
简而言之,没有
BeanDefinitionRegistry
,
Spring
会失去中心化的
Bean
管理,导致效率下降、错误处理分散、以及增加生命周期管理的复杂度。
BeanDefinitionRegistry
确保了
Spring
的高效、一致和稳定运行。
3. BeanDefinitionRegistry 的使用
3.1 BeanDefinitionRegistry 简单例子
在这个例子中,我们将创建一个简单的
Bean
,注册到
DefaultListableBeanFactory
(它实现了
BeanDefinitionRegistry
接口),然后从工厂中获取并使用这个
Bean
。
全部代码如下:
首先,我们需要一个
Bean
类,这是一个简单的
POJO
类:
packagecom.example.demo.bean;publicclassMyBean{privateString message;publicvoiddoSomething(){System.out.println("Hello, world!");}publicvoidsetMessage(String message){this.message = message;}publicvoidgetMessage(){System.out.println("Your Message : "+ message);}}
然后,我们可以使用
DefaultListableBeanFactory
和
RootBeanDefinition
来创建并注册这个
Bean
:
packagecom.example.demo;importcom.example.demo.bean.MyBean;importorg.springframework.beans.factory.config.BeanDefinition;importorg.springframework.beans.factory.support.DefaultListableBeanFactory;importorg.springframework.beans.factory.support.RootBeanDefinition;publicclassDemoApplication{publicstaticvoidmain(String[] args){// 创建 BeanDefinitionRegistryDefaultListableBeanFactory registry =newDefaultListableBeanFactory();// 创建一个 BeanDefinitionBeanDefinition beanDefinition =newRootBeanDefinition(MyBean.class);// 注册 BeanDefinition
registry.registerBeanDefinition("myBean", beanDefinition);// 从 BeanFactory 中获取 BeanMyBean myBean = registry.getBean("myBean",MyBean.class);// 使用 Bean
myBean.doSomething();// 输出:Hello, world!}}
这个程序会创建一个名为
"myBean"
的
Bean
,这个
Bean
是
MyBean
类的一个实例。然后,我们从
BeanFactory
中获取这个
Bean
,并调用其
doSomething
方法,打印
"Hello, world!"
。
3.2 有关ImportBeanDefinitionRegistrar的实现类的例子
这个在第
8
篇(Spring高手之路8——Spring Bean模块装配的艺术:@Import详解)提到过,是
3.5
节,大家可以回头看,这里不重复粘贴代码。
4. BeanDefinition的合并
我们前一篇讲解
BeanDefinition
的时候没有讲解
BeanDefinition
的合并,这里补充说明。
- BeanDefinition
在
Spring
中,
BeanDefinition
是一个接口,它定义了
Bean
的配置信息,例如
Bean
的类名,是否是单例,依赖关系等。在
Spring
中,每一个
Bean
都对应一个
BeanDefinition
对象。
- 合并的意义
在
Spring
中,有一种特殊的
BeanDefinition
,叫做子
BeanDefinition
,也就是我们在
XML
配置文件中通过
parent
属性指定的那种。这种子
BeanDefinition
可以继承父
BeanDefinition
的配置信息。
合并的过程,就是把子
BeanDefinition
的配置信息和父
BeanDefinition
的配置信息合并起来,形成一个完整的配置信息。合并后的
BeanDefinition
对象包含了
Bean
创建所需要的所有信息,
Spring
将使用这个完整的
BeanDefinition
来创建
Bean
实例。
- 合并的过程
Spring
在需要创建
Bean
实例的时候,会先获取对应的
BeanDefinition
对象。如果这个
BeanDefinition
是一个子
BeanDefinition
,
Spring
就会找到它的父
BeanDefinition
,然后把两者的配置信息合并起来,形成一个完整的
BeanDefinition
。
这个过程是在
DefaultListableBeanFactory
的
getMergedBeanDefinition
方法中进行的,如果大家有兴趣,可以在这个方法中设置断点,看一看具体的合并过程。
- 合并过程的流程图
我们可以通过父子
Bean
的方式使用这个特性,下面是一个
XML
配置的例子:
<beanid="parentBean"class="com.example.ParentClass"abstract="true"><propertyname="commonProperty"value="commonValue"/></bean><beanid="childBean"parent="parentBean"><propertyname="specificProperty"value="specificValue"/></bean>
在这个例子中,我们定义了两个
bean
,一个是
parentBean
,另一个是
childBean
。
parentBean
是
abstract
的,表示它不会被实例化,只作为模板使用。
childBean
的
parent
属性指向
parentBean
,表示它继承了
parentBean
的配置。
parentBean
有一个属性
commonProperty
,值为
commonValue
。
childBean
有一个属性
specificProperty
,值为
specificValue
。在
Spring
解析这个配置文件,生成
BeanDefinition
的时候,
childBean
的
BeanDefinition
会包含两个属性:
commonProperty
和
specificProperty
,这就是
BeanDefinition
的合并过程。
**在
Java
配置中,我们无法直接模拟
XML
配置的
BeanDefinition
合并过程**,因为这是
Spring XML
配置的一项特性,配置类通常会采用
Java
代码的继承或组合来重用
bean
定义,不会涉及到配置元数据层面的
BeanDefinition
合并。
XML
配置中的
BeanDefinition
合并特性允许我们定义一个父
Bean
,然后定义一些子
Bean
,子
Bean
可以继承父
Bean
的一些属性。
这个特性在
Java
配置中并没有直接的替代品,因为
Java
配置通常更加依赖实例化过程中的逻辑,而不是元数据(即
BeanDefinition
)。在
Java
配置中,我们可以使用继承和组合等普通的
Java
特性来实现类似的结果,但这不是真正的
BeanDefinition
合并。因此,当我们从
XML
配置转换为
Java
配置时,通常需要手动将共享的属性复制到每个
Bean
的定义中。
4.1 调试验证BeanDefinition的合并
全部代码如下:
首先,创建
XML
配置文件
applicationContext.xml
:
<?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="parentBean"class="com.example.demo.bean.ParentClass"abstract="true"><property name="commonProperty" value="commonValue"/></bean><bean id="childBean" parent="parentBean"class="com.example.demo.bean.ChildClass"><property name="specificProperty" value="specificValue"/></bean></beans>
然后,创建
ParentClass
和
ChildClass
,如下:
packagecom.example.demo.bean;publicabstractclassParentClass{privateString commonProperty;publicStringgetCommonProperty(){return commonProperty;}publicvoidsetCommonProperty(String commonProperty){this.commonProperty = commonProperty;}}
packagecom.example.demo.bean;publicclassChildClassextendsParentClass{privateString specificProperty;publicStringgetSpecificProperty(){return specificProperty;}publicvoidsetSpecificProperty(String specificProperty){this.specificProperty = specificProperty;}}
主程序如下:
packagecom.example.demo;importorg.springframework.beans.factory.config.BeanDefinition;importorg.springframework.beans.factory.support.DefaultListableBeanFactory;importorg.springframework.context.support.ClassPathXmlApplicationContext;publicclassDemoApplication{publicstaticvoidmain(String[] args){ClassPathXmlApplicationContext context =newClassPathXmlApplicationContext("applicationContext.xml");DefaultListableBeanFactory factory =(DefaultListableBeanFactory) context.getBeanFactory();// 获取childBean的原始BeanDefinitionBeanDefinition childBeanDefinition = factory.getBeanDefinition("childBean");System.out.println("Child bean definition before merge: "+ childBeanDefinition);// 获取合并后的BeanDefinitionBeanDefinition mergedBeanDefinition = factory.getMergedBeanDefinition("childBean");System.out.println("Merged bean definition: "+ mergedBeanDefinition);}}
在这个示例中,我们首先加载了
applicationContext.xml
配置文件,然后获取了
childBean
的原始
BeanDefinition
。然后,我们调用
getMergedBeanDefinition
方法获取了合并后的
BeanDefinition
,可以在这个过程中设置断点来查看合并过程的详细情况。
运行结果:
大家可以从运行结果看到打印了
Generic bean
和
Root bean
,代表了
GenericBeanDefinition
和
RootBeanDefinition
。
为什么有两个不同的 BeanDefinition 类型( GenericBeanDefinition 和 RootBeanDefinition)?
GenericBeanDefinition:
- 这是一个通用的
BeanDefinition
实现类,可以配置任何类型的bean
。 - 它通常用于读取
XML
、注解或其他形式的配置。 - 与其它特定的
BeanDefinition
相比,它是比较简单和轻量级的。 - 当使用
<bean>
元素在XML
中定义bean
时,通常会为该bean
创建一个GenericBeanDefinition
实例。
RootBeanDefinition:
- 这是一个完整的
bean
定义,包含了bean
的所有配置信息,如构造函数参数、属性值、方法覆盖等。 - 它通常用于合并父子
bean
定义。也就是说,当一个bean
定义继承另一个bean
定义时,RootBeanDefinition
负责持有合并后的最终配置。 - 除了
GenericBeanDefinition
之外,它还包含许多与bean
的实例化、依赖解析和初始化相关的内部细节。 - 在
Spring
的内部工作流中,尽管开始时可以有各种BeanDefinition
实现,但在容器的后期处理阶段,它们通常都会转化为RootBeanDefinition
,因为在这个阶段需要一个完整和固定的bean
定义来进行bean
的创建。
调试点1:我们从
BeanFactory
中获取了子
bean
的原始
BeanDefinition
。这个
BeanDefinition
只表示了
XML
中为子
bean
配置的元数据,没有与父
Bean
的合并,只能看到
specificProperty
属性。
调试点2:用
getMergedBeanDefinition
之后,控制台打印的
BeanDefinition
的类型变为了
RootBeanDefinition
,此时,我们从
BeanFactory
中获取了合并后的子
Bean
的
BeanDefinition
。由于子
Bean
的
BeanDefinition
与父
Bean
的
BeanDefinition
已合并,所以能看到一个完整的属性集,这里在
propertyValues
中看到两个属性键值对:
commonProperty
和
specificProperty
,这表明子
Bean
继承了父
Bean
的属性值。
注意,这个示例的目的是展示
BeanDefinition
的合并过程,因此我们直接操作了
BeanFactory
。在实际的应用开发中,我们一般不会直接操作
BeanFactory
。
4.2 BeanDefinition合并的目的
- 提供完整的BeanDefinition信息:在配置中,我们经常会使用父子
BeanDefinition
(如通过<bean>
标签的parent
属性)。子BeanDefinition
可能只会定义需要改变或增加的bean
属性,而父BeanDefinition
则提供共享的默认定义。在这种情况下,合并操作会将父子BeanDefinition
的信息合并为一个完整的BeanDefinition
,用于接下来的bean
创建。 - 优化性能:合并操作的结果通常会被缓存起来,因此在下次获取同样的
bean
时,可以直接从缓存中获取合并后的BeanDefinition
,避免了重复的合并操作,从而提高了性能。 - 解决循环依赖:在处理
bean
之间的循环依赖时,需要尽早抛出已经处理(例如实例化和属性填充)的bean
,这时就需要一个完整的BeanDefinition
信息。因此,BeanDefinition
的合并在解决循环依赖问题上也有重要作用。
简而言之,
BeanDefinition
的合并是为了得到一个完整、准确的
BeanDefinition
,以供
Spring IoC
容器后续的
bean
创建和依赖解析使用。
4.3 图解BeanDefinition合并与Spring初始化关系
- 资源定位
在此阶段,
Spring
会根据用户的配置来确定需要加载的资源位置,资源可能来源于多种配置方式,如
XML
、
Java
注解或
Java
配置。
- 读取配置
Spring
从确定的配置源中读取
Bean
定义信息
- 对于
XML
配置,解析器会处理每一个<bean>
元素。在这个时候,特别是存在父子Bean
关系的定义,这些定义被解析为原始的BeanDefinition
,但并没有合并。 - 对于注解和
Java
配置,BeanDefinition
被解析为独立的定义,通常不涉及父子关系。
- 注册BeanDefinition
Spring
会将所有解析得到的
BeanDefinition
注册到
BeanDefinitionRegistry
中。
- 处理BeanDefinition
在这个阶段,
Spring
进行
BeanDefinition
的预处理。
- 如果从
XML
配置中读取的Bean
之间存在父子关系,这时会进行合并,合并后的BeanDefinition
确保子Bean
继承了父Bean
的所有属性,并且能够覆盖它们。 - 而基于注解或
Java
配置的Bean
定义,由于没有明确的父子关系,这种合并操作通常不会发生。
- Bean的实例化与属性填充
- 此阶段标志着
Spring
生命周期的开始。 - 所有的
BeanDefinition
,无论是原始的还是经过合并的,都会在此阶段转化为实际的Bean
实例。 Spring
容器将负责管理这些Bean
的完整生命周期,包括但不限于依赖注入、属性设置。
- Bean的初始化
- 包括调用
Bean
的初始化方法,例如实现了InitializingBean
接口的afterPropertiesSet
方法或者通过init-method
属性指定的自定义初始化方法。 - 在此阶段,
Bean
已经完全准备好,可以供应用程序使用。
- 注册Bean的销毁方法
Spring
会跟踪并注册Bean
的销毁方法。- 这确保了当
Spring
容器关闭时,它会正确地调用每个Bean
的销毁方法,例如实现了DisposableBean
接口的destroy
方法或通过destroy-method
属性指定的自定义方法。
从这里可以看到
BeanDefinition
的合并发生在实际
Bean
实例化之前的早期阶段,这确保了当
Spring
去创建一个
Bean
实例时,它有一个完整的、合并的定义可以依赖。
5. BeanDefinition的合并的源码分析
5.1 BeanDefinition合并过程时序图
5.2 BeanDefinition合并过程源码解读
这里讲一下前一篇没提到的
BeanDefinition
的合并,我们针对
Spring 5.3.7
的源码分析一下,先展示图,后面分析。
我们分析一下
AbstractBeanFactory
类的几个方法。
// 获取本地合并后的 BeanDefinitionprotectedRootBeanDefinitiongetMergedLocalBeanDefinition(String beanName)throwsBeansException{// 从缓存中获取合并后的 BeanDefinitionRootBeanDefinition mbd =(RootBeanDefinition)this.mergedBeanDefinitions.get(beanName);// 如果缓存中的 BeanDefinition 不为空并且未过时,则直接返回// 否则,对 BeanDefinition 进行合并return mbd !=null&&!mbd.stale ? mbd :this.getMergedBeanDefinition(beanName,this.getBeanDefinition(beanName));}// 获取合并后的 BeanDefinitionprotectedRootBeanDefinitiongetMergedBeanDefinition(String beanName,BeanDefinition bd)throwsBeanDefinitionStoreException{// 直接调用 getMergedBeanDefinition 方法,将 containingBd 设为 nullreturnthis.getMergedBeanDefinition(beanName, bd,(BeanDefinition)null);}// 获取合并后的 BeanDefinitionprotectedRootBeanDefinitiongetMergedBeanDefinition(String beanName,BeanDefinition bd,@NullableBeanDefinition containingBd)throwsBeanDefinitionStoreException{synchronized(this.mergedBeanDefinitions){RootBeanDefinition mbd =null;RootBeanDefinition previous =null;// 如果没有包含的 BeanDefinition,那么从缓存中获取合并后的 BeanDefinitionif(containingBd ==null){
mbd =(RootBeanDefinition)this.mergedBeanDefinitions.get(beanName);}// 如果缓存中的 BeanDefinition 为空或者过时,那么创建新的 BeanDefinition 进行合并if(mbd ==null|| mbd.stale){
previous = mbd;// 如果 bd 没有父名称,即没有继承其他的 bean// 那么就直接 clone 这个 bd,生成一个 RootBeanDefinitionif(bd.getParentName()==null){if(bd instanceofRootBeanDefinition){
mbd =((RootBeanDefinition)bd).cloneBeanDefinition();}else{
mbd =newRootBeanDefinition(bd);}}else{// 如果 bd 是一个子 BeanDefinition(即有父 BeanDefinition)// 首先获取父 BeanDefinitionBeanDefinition pbd;try{String parentBeanName =this.transformedBeanName(bd.getParentName());if(!beanName.equals(parentBeanName)){
pbd =this.getMergedBeanDefinition(parentBeanName);}else{BeanFactory parent =this.getParentBeanFactory();if(!(parent instanceofConfigurableBeanFactory)){thrownewNoSuchBeanDefinitionException(parentBeanName,"Parent name '"+ parentBeanName +"' is equal to bean name '"+ beanName +"': cannot be resolved without a ConfigurableBeanFactory parent");}
pbd =((ConfigurableBeanFactory)parent).getMergedBeanDefinition(parentBeanName);}}catch(NoSuchBeanDefinitionException var11){thrownewBeanDefinitionStoreException(bd.getResourceDescription(), beanName,"Could not resolve parent bean definition '"+ bd.getParentName()+"'", var11);}// 创建一个新的 RootBeanDefinition 并覆盖 bd 中的属性// 这就完成了父子 BeanDefinition 的合并
mbd =newRootBeanDefinition(pbd);
mbd.overrideFrom(bd);}// 如果合并后的 BeanDefinition 没有指定作用域// 则默认设置为 singletonif(!StringUtils.hasLength(mbd.getScope())){
mbd.setScope("singleton");}// 如果定义了父 BeanDefinition 且父 BeanDefinition 的作用域不是 singleton 但子 BeanDefinition 的作用域是 singleton// 则将子 BeanDefinition 的作用域设置为父 BeanDefinition 的作用域if(containingBd !=null&&!containingBd.isSingleton()&& mbd.isSingleton()){
mbd.setScope(containingBd.getScope());}// 如果不存在包含的 BeanDefinition 并且需要缓存 BeanMetadata// 那么就将这个新创建并合并的 BeanDefinition 放入 mergedBeanDefinitions 缓存中if(containingBd ==null&&this.isCacheBeanMetadata()){this.mergedBeanDefinitions.put(beanName, mbd);}}// 如果之前存在过期的 BeanDefinition// 那么从过期的 BeanDefinition 中拷贝相关的缓存到新的 BeanDefinition 中if(previous !=null){this.copyRelevantMergedBeanDefinitionCaches(previous, mbd);}// 返回合并后的 BeanDefinitionreturn mbd;}}
这段代码主要完成了
BeanDefinition
的合并工作。当一个
BeanDefinition
有父
BeanDefinition
时,
Spring
会将子
BeanDefinition
的定义与父
BeanDefinition
的定义进行合并,生成一个新的完整的
BeanDefinition
,这个过程就是
BeanDefinition
的合并。该合并的
BeanDefinition
会被缓存起来,以便下次使用。如果一个
BeanDefinition
没有父
BeanDefinition
,则直接
clone
一份作为合并后的
BeanDefinition
。在
Spring
的整个生命周期中,
BeanDefinition
的合并可能会发生多次,每次获取
Bean
时,都会先进行
BeanDefinition
的合并。
欢迎一键三连~
有问题请留言,大家一起探讨学习
----------------------Talk is cheap, show me the code-----------------------
版权归原作者 砖业洋__ 所有, 如有侵权,请联系我们删除。