这一节我们将从spring底层源码的角度为大家剖析spring构造bean实例的细节。
源码调试技巧
我们的源码调试原则:基于当前的demo和调试目标(弄懂哪块源码),无视其他的逻辑判断分支,按主线索往下走。
之前我们在application-context中定义的各种bean,会在我们调用new ClassPathXmlApplicationContext("application-context.xml")时随着spring容器启动时进行这些bean定义的加载解析,然后获取所有单例bean,不存在实例的情况下进行构造。这里提到bean的作用域,单例(singleton)是其中一种,也是最重要的一种,大家可以理解为该bean在整个spring容器中存在唯一的实例。而这些bean,会随着spring容器的启动被提前创建出来,并进行缓存,而应用层,也就是我们用户在调用相关的getBean方法从容器获取时,容器会直接从缓存中返回我们需要的实例。
而容器的内部按照我们定义的bean,完成定义的注册以后,也会调用相应的getBean方法来进行单例bean的创建和缓存,以及获取被依赖的bean来完成bean的依赖注入。这个getBean方法就是AbstractBeanFactory类的如下方法:
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
我们发现,该方法只有一个name入参,实际会将其作为参数调用一个doGetBean方法,该方法的其他参数进行了硬编码,因为它是被内部调用的。
接下来我们将跳过doGetBean获取bean的细节,直奔主题的定位到我们要关注的用来构造bean实例的入口方法。关于bean是如何获取、如何缓存的整个流程会在本系列教程后续章节中为大家揭秘。

该方法我们只要关心传过来的前面两个实参,重点是第二个参数,其中包含了获取已注册的bean定义解析后的bean定义对象。
我们的bean定义:
<bean id="funnyBook" class="com.xiaoma.spring.example.reading.Book">
<property name="name" value="葵花宝典" />
<property name="type" value="武功秘籍" />
</bean>
<bean id="dabao" class="com.xiaoma.spring.example.reading.Child">
<constructor-arg name="name" value="大宝" />
<!-- 通过ref属性引用其他bean -->
<constructor-arg name="book" ref="funnyBook" />
</bean>
以上我们定义的bean会最终解析为bean定义对象,创建bean实例需要的元数据信息都可以从bean定义对象中获取。关于这个定义对象中具体存些什么信息我们不会关心太多,按照我们的定义,<bean>中提供的class属性值最终会设置到定义对象的beanClass属性中。id为funnyBook的bean,采用的是无参构造创建的,具体调用方法为AbstractAutowireCapableBeanFactory的instantiateBean(String beanName, RootBeanDefinition mbd)方法:

看到代码调试行,很显然这里采用了策略设计模式获取bean的实例化策略,这里我们采用默认的策略实现CglibSubclassingInstantiationStrategy类的实例,而它是继承自SimpleInstantiationStrategy,它们都实现了InstantiationStrategy接口。看下该接口的定义:
package org.springframework.beans.factory.support;
import ...
public interface InstantiationStrategy {
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner)
throws BeansException;
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
Constructor<?> ctor, Object... args) throws BeansException;
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
@Nullable Object factoryBean, Method factoryMethod, Object... args)
throws BeansException;
}
很显然,该接口对于无参构造、有参构造和工厂模式创建bean的形式都提供了相应的方法定义。SimpleInstantiationStrategy对它们做了基本的实现,而CglibSubclassingInstantiationStrategy又扩展出了当bean定义中出现方法覆盖时如何构建bean实例的相关实现。这里关于BeanDefinition的methodOverride相关的话题我们本节不做讨论。
继续往下调试:

以上的逻辑在获取到用来创建bean的无参构造器(调用class.getDeclaredConstructor())后会将结构缓存到bean定义对象中,下次就可以直接获取,考虑到线程安全问题,会将对构造器的获取和设置其到缓存中的操作放在同步代码块中。最终的实例化工作交给了BeanUtils相应的instantiateClass方法。
很显然,我们这里调用的是无参构造,参数列表为空。
对于dabao这个bean,我们定义了构造器参数,转换为beanDefinition对象中如下属性:

现在需要将参数注入构造器,调用的是:

决定使用哪个有参构造的逻辑比较复杂,beanFactory交给了一个新new出来的ConstructorResolver实例,调用其autowireConstructor方法来完成。

按照我们的调试原则,看一下代码的执行路径

BeanDefinitionValueResolver负责对bean定义中的各种值进行解析,比如提供的字符串值中出现表达式的情况,或者值为一个bean引用的情况等等。基于我们这里的示例,我们重点关注的是如何解析bean引用(RuntimeBeanReference)的情况。
最为复杂的是如何根据从bean定义中解析出来的ConstructorArgumentValues类型的resolvedValues去匹配到最合适的构造器candidate来构造bean的实例,下一节将着重对其揭秘。

文章详细阐述了Spring框架在启动时如何从XML配置文件加载bean定义,特别是对于单例bean的构造和缓存过程。通过分析`AbstractBeanFactory`的`getBean`方法,揭示了bean实例化的核心逻辑,包括使用`InstantiationStrategy`接口的实现如`SimpleInstantiationStrategy`和`CglibSubclassingInstantiationStrategy`。文章还提到了bean定义中的构造器参数解析,特别是处理bean引用的过程。
883

被折叠的 条评论
为什么被折叠?



