构造器创建bean底层源码解析

这一节我们将从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,采用的是无参构造创建的,具体调用方法为AbstractAutowireCapableBeanFactoryinstantiateBean(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实例的相关实现。这里关于BeanDefinitionmethodOverride相关的话题我们本节不做讨论。

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

对于dabao这个bean,我们定义了构造器参数,转换为beanDefinition对象中如下属性:
在这里插入图片描述
现在需要将参数注入构造器,调用的是:
在这里插入图片描述
决定使用哪个有参构造的逻辑比较复杂,beanFactory交给了一个新new出来的ConstructorResolver实例,调用其autowireConstructor方法来完成。
在这里插入图片描述
按照我们的调试原则,看一下代码的执行路径
在这里插入图片描述
BeanDefinitionValueResolver负责对bean定义中的各种值进行解析,比如提供的字符串值中出现表达式的情况,或者值为一个bean引用的情况等等。基于我们这里的示例,我们重点关注的是如何解析bean引用(RuntimeBeanReference)的情况。

最为复杂的是如何根据从bean定义中解析出来的ConstructorArgumentValues类型的resolvedValues去匹配到最合适的构造器candidate来构造bean的实例,下一节将着重对其揭秘。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java小卷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值