这一节我们将从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的实例,下一节将着重对其揭秘。