1. 前言
上节我们介绍了 BeanFactory
的继承体系,实现了一个最简单的 Spring 容器。从严格意义上来说,此时的 BeanFactory
不能称为 IOC 容器,这是因为注册的对象是从外部传入的。换言之,BeanFactory
并没有掌握对象的创建权,控制反转也就无从谈起。
在现实世界中,有了图纸和规格说明书,工厂就能生产符合预期的产品。BeanFactory
也是如此,想要自行创建对象,就要按图索骥。那么什么是图纸,又该如何索骥,这是本节着重讨论的两个问题。
2. BeanDefinition
2.1 概述
一般来说,创建对象是通过 new 关键字完成的。如果我们把这种方式看做手工作坊,那么 Spring 容器采用就是自动化生产。在自动化生产中,最重要的就是标准,BeanDefinition
包含了创建一个对象所需的各种信息。换句话说,BeanDefinition
是 Spring 容器创建对象的依据。BeanDefinition
的继承结构比较复杂,我们只介绍最核心的四个接口和类。
BeanDefinition
:核心接口,定义了一些常量以及获取相关属性的方法AbstractBeanDefinition
:实现了BeanDefinition
接口,定义了与创建 Bean 有关的属性RootBeanDefinition
:最重要的实现类,Spring 容器以此作为创建实例的依据AnnotatedBeanDefinition
:通过注解的方式来创建实例
此外,AnnotatedBeanDefinition
接口有三个实现类,其中 ConfigurationClassBeanDefinition
和 ScannedGenericBeanDefinition
使用红色标识,表示它们不属于 beans 模块。Spring 使用这三个类提供了灵活且方便的创建 Bean 的方式,我们将在第三章 context 模块详细介绍。
2.2 AbstractBeanDefinition
AbstractBeanDefinition
持有一系列描述创建对象的属性,BeanDefinition
接口则定义了获取这些属性的方法。我们直接来看这些属性的作用,如下所示:
beanClass
:作为最重要的属性,表示待创建对象的类型。该字段可能是Class
或String
类型,且最终会被转换为Class
类型。scope
:表示对象的作用域。BeanDefinition
接口定义了两种最常用的作用域,单例(singleton)表示在整个应用中获取的是同一个对象,原型(prototype)表示每次获取一个新的对象。单例是由 Spring 容器直接管理的,这是我们唯一关注的。本教程所说的组件或 Bean 都是单例作用域,不涉及原型作用域。role
:表示对象的角色。BeanDefinition
接口定义了三种角色,常用的有两种。ROLE_APPLICATION
表示该对象是由应用程序创建的,这是默认的情况。ROLE_INFRASTRUCTURE
是指 Spring 框架自身创建的对象,提供一些基础功能,需要特殊标注出来。ROLE_SUPPORT
几乎用不到,不予讨论。primary
:在依赖注入时,如果存在多个候选项,可以优先选择被标记为primary
的单例。这是一个很有用的属性,比如应用程序中存在多个数据源,可以将其中一个标记为主数据源。propertyValues
:存储一组属性值,通过属性访问的方式赋给目标对象。synthetic
:表示一个对象是「合成的」。与 role 属性有点类似,也是一种特殊的对象,可以绕过框架中的某些处理逻辑,常见的用法的是可以绕过BeanPostProcessor
的执行。
public abstract class AbstractBeanDefinition extends AttributeAccessorSupport implements BeanDefinition {
private volatile Object beanClass;
private String scope = "";
private int role = BeanDefinition.ROLE_APPLICATION;
private boolean primary;
private MutablePropertyValues propertyValues;
private boolean synthetic = false;
}
2.3 RootBeanDefinition
RootBeanDefinition
作为最重要的实现类,是 Spring 容器创建对象的标准依据。具体说来,Spring 容器在注册 BeanDefinition
时,虽然可以直接创建 RootBeanDefinition
对象,但更普遍的做法是使用 AnnotatedBeanDefinition
接口的实现类。它们之间有着细微的差别,最终都要转换成 RootBeanDefinition
。
RootBeanDefinition
的大多数字段是默认的包访问权限,仅供 BeanFactory
内部使用。构造方法也值得注意,其中一个需要指定 Class
类型的参数,这说明可以用反射的方式创建对象。另一个构造方法需要传入一个 BeanDefinition
实例,说明可以将其他的实现类转换成标准结构。
public class RootBeanDefinition extends AbstractBeanDefinition {
volatile Class<?> resolvedTargetType;
public RootBeanDefinition(Class<?> beanClass){
super();
setBeanClass(beanClass);
}
public RootBeanDefinition(BeanDefinition original){
super(original);
}
}
3. BeanDefinition的管理
3.1 BeanDefinitionRegistry
BeanDefinitionRegistry
接口的作用是管理 BeanDefinition
,当我们调用 BeanFactory
接口的 getBean
方法时,前提是先将 BeanDefinition
注册到容器中。
registerBeanDefinition
方法:将BeanDefinition
注册到容器containsBeanDefinition
方法:是否持有指定名称的BeanDefinition
getBeanDefinition
方法:获取指定名称的BeanDefinition
getBeanDefinitionNames
方法:返回所有BeanDefinition
的名称
public interface BeanDefinitionRegistry {
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
boolean containsBeanDefinition(String beanName);
BeanDefinition getBeanDefinition(String beanName);
String[] getBeanDefinitionNames();
}
3.2 BeanFactory的调整
由于引入了 BeanDefinition
,需要对 BeanFactory
以下的接口和类进行调整。ConfigurableBeanFactory
接口定义了获取 BeanDefinition
的方法,两个方法稍有区别:
getMergedBeanDefinition
方法:获取合并后的BeanDefinition
。这里要注意两个问题,一是如果两个BeanDefinition
是父子关系,则合并为一个。二是返回的是RootBeanDefinition
,这是创建对象的标准依据。getBeanDefinition
方法:获取BeanDefinition
实例,返回的是接口类型,可以是任意实现类。
public interface ConfigurableBeanFactory extends AutowireCapableBeanFactory, SingletonBeanRegistry {
RootBeanDefinition getMergedBeanDefinition(String beanName) throws BeansException;
BeanDefinition getBeanDefinition(String beanName);
}
AbstractBeanFactory
持有 mergedBeanDefinitions
字段,负责存储 RootBeanDefinition
。该类还实现了 ConfigurableBeanFactory
接口的 getMergedBeanDefinition
方法,代码略。
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
private final Map<String, RootBeanDefinition> mergedBeanDefinitions = new ConcurrentHashMap<>(256);
@Override
public RootBeanDefinition getMergedBeanDefinition(String beanName) {...}
}
DefaultListableBeanFactory
实现了 BeanDefinitionRegistry
接口,beanDefinitionMap
字段的作用是存储 BeanDefinition
。此外,还实现了 registerBeanDefinition
方法,将 BeanDefinition
对象添加到缓存中。
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements BeanDefinitionRegistry {
//BeanDefinition名称列表
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
//BeanDefinition的缓存
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
this.beanDefinitionMap.put(beanName, beanDefinition);
if(existingDefinition == null){
beanDefinitionNames.add(beanName);
}
}
//其余方法实现简单,代码略
}
深度思考:既然
BeanDefinition
的管理工作是如此重要,为什么BeanDefinitionRegistry
接口孤悬于整个继承体系之外?换句话说,为什么不把BeanDefinitionRegistry
接口的方法定义在BeanFactory
接口中,这样岂不是更加省事。读者可以试着从 Spring 框架作者的角度思考一下原因,我们将在第三章 context 模块中进行讨论。
3.3 设计思路
Spring 将 BeanDefinition
分为两级缓存,其中 DefaultListableBeanFactory
的 beanDefinitionMap
字段是二级缓存,存储 BeanDefinition
接口的实现类。AbstractBeanFactory
的 mergedBeanDefinitions
字段是一级缓存,且限定类型必须是 RootBeanDefinition
。
如图所示,先把 RootBeanDefinition
、GenericBeanDefinition
、AnnotatedBeanDefinition
接口的实现类等注册到二级缓存中。然后在执行创建流程时,对二级缓存中的 BeanDefinition
进行处理,转换成 RootBeanDefinition
并保存到一级缓存中。从这里可以看到,RootBeanDefinition
是创建对象的依据,其他实现类则是适用不同情况的权宜之计。
4. doGetBean方法详解
4.1 获取Bean的流程
AbstractBeanFactory
实现了 BeanFactory
接口的一系列 getBean
方法,它们最终都会调用 doGetBean
方法。我们先来从全局观察整个方法,大体可以分为三步。
- 单例已存在容器中,直接从缓存中获取。
- 如果单例不存在,需要通过
BeanDefinition
来创建对象,并注册到容器中。 - 拿到单例之后,还要检查是否与所需的类型一致,如果不一致需要进行类型转换。
第一步,首先尝试从缓存中获取 Bean,如果不存在,那么还有一种可能,就是 FactoryBean
类型的特殊单例。本节我们只考虑普通单例,暂不介绍 getObjectForBeanInstance
方法的具体实现。
/**
* 所属类[cn.stimd.spring.beans.factory.support.AbstractBeanFactory]
*
* BeanFactory的核心方法,获取实例
* @param name Bean的名称,可能带有&前缀
* @param requiredType Bean的类型
*/
protected <T> T doGetBean(final String name, final Class<T> requiredType) {
Object bean;
String beanName = BeanFactoryUtils.transformedBeanName(name);
//1. 从缓存中获取对象
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null) {
//TODO FactoryBean的处理
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
//2. 缓存中不存在,则通过BeanDefinition来创建Bean,并加入缓存
else {
//2.1 检查父容器中是否存在Bean
if(this.parentBeanFactory != null && !containsBeanDefinition(beanName)){
return parentBeanFactory.getBean(beanName, requiredType);
}
//2.2 获取BeanDefinition
final RootBeanDefinition mbd = getMergedBeanDefinition(beanName);
//2.3 创建单例Bean并注册到容器中
sharedInstance = getSingleton(beanName, new ObjectFactory(){
@Override
public Object getObject() throws BeansException {
//模版方法,实际的创建逻辑由子类实现
return createBean(beanName, mbd);
}
});
//TODO 2.4 FactoryBean的处理
bean = getObjectForBeanInstance(sharedInstance, name, beanName);
}
//3. 如果Bean不是requiredType的实例,则进行必要的类型转换
if (requiredType != null && !requiredType.isInstance(bean)) {
return getTypeConverter().convertIfNecessary(bean, requiredType);
}
return (T) bean;
}
第二步,如果单例不存在,那么就需要创建对象,然后加入缓存中。这一步骤可以分解为四个阶段,如下:
- 检查父容器是否存在,如果存在尝试从父容器中获取单例,也就是递归调用
doGetBean
方法。 - 获取对应的
BeanDefinition
,调用getMergedBeanDefinition
方法确保拿到的是RootBeanDefinition
对象。 - 调用
getSingleton
方法,通过回调ObjectFactory
匿名对象的getObject
方法,间接调用createBean
方法,完成对象的创建工作。 - 如果单例的类型是
FactoryBean
,还需要进一步的处理。(待实现)
我们来看 getSingleton
方法的实现,这是另一个重载的同名方法,多了一个 ObjectFactory
类型的参数。首先检查缓存中是否存在单例,如果不存在,回调 ObjectFactory
接口的 getObject
方法,该方法内部调用 createBean
方法创建实例(具体创建流程见下文)。最后调用 addSingleton
方法把创建的实例添加到缓存中。
/**
* 所属类[cn.stimd.spring.beans.factory.support.DefaultSingletonBeanRegistry]
*
* 尝试从缓存中获取实例,如不存在则创建实例
* @param beanName Bean的名称
* @param singletonFactory 回调接口,执行创建流程
*/
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (singletonObjects){
//尝试获取缓存
Object singletonObject = this.singletonObjects.get(beanName);
if(singletonObject == null){
//回调接口,执行创建Bean的流程
singletonObject = singletonFactory.getObject();
//将单例添加到缓存中
addSingleton(beanName, singletonObject);
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
第三步,此时已经拿到了单例对象,但可能与需要的类型不一致,因此需要通过 TypeConverter
进行类型转换。总的来说,doGetBean
方法完成了一些外围工作,比如尝试从缓存或父容器中获取实例、对 FactoryBean
类型单例的处理,以及必要的类型转换。
4.2 创建Bean的流程
createBean
方法是由子类 AbstractAutowireCapableBeanFactory
实现的,但该方法完成了一些外围工作,真正的创建逻辑是由 doCreateBean
方法完成的。doCreateBean
方法可以划分为五个步骤,每一步都很重要,本章的后续内容主要是围绕这几步展开的。简单介绍如下:
- Bean 的实例化,也就是创建目标对象。
- 在实现依赖注入功能时,循环依赖是一件棘手的事情,需要进行一定的处理。
- 填充对象,主要是对字段进行赋值,依赖注入的功能就是在这一步实现的。
- 此时已经得到了一个相对完整的实例,初始化的作用主要是提供了一定的扩展,允许用户对实例进行定制化,完成某些自定义的处理逻辑。
- 当 Spring 容器关闭时,有一些特殊的实例需要执行销毁操作,大多是携带资源的组件,比如数据库连接池等。因此需要对这些特殊的实例进行管理,先注册到 Spring 容器中,等到关闭容器时触发。
本节我们只关心第一步,也就是 Bean 的实例化。首先调用 createBeanInstance
方法创建实例,该方法返回一个 BeanWrapper
对象,继而调用 getWrappedInstance
方法得到真正的对象。前边提到,createBean
方法是以接口回调的方式执行的,因此 doCreateBean
方法返回的对象将被注册到 Spring 容器中。
//所属类[cn.stimd.spring.beans.factory.support.AbstractAutowireCapableBeanFactory]
//创建对象的流程
protected Object doCreateBean(String beanName, RootBeanDefinition mbd) {
//1. Bean的实例化
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd);
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
mbd.resolvedTargetType = beanType;
//2. 解决循环依赖(TODO)
//3. 填充对象(TODO)
//4. Bean的初始化(TODO)
//5. 注册需要销毁的实例(TODO)
return bean;
}
我们来看 createBeanInstance
方法的实现,该方法提供了两种实例化的方式,一是通过工厂方法的方式,这个后边会提到,先略过。二是通过无参的构造器,实际的创建工作委托给 InstantiationStrategy
接口的实现类来完成,SimpleInstantiationStrategy
通过反射的方式创建对象。
注:在源码中,默认的实例化策略实现类是
CglibSubclassingInstantiationStrategy
,我们并不关心这种实现,感兴趣的读者可以自行完成。
//所属类[cn.stimd.spring.beans.factory.support.AbstractAutowireCapableBeanFactory]
//对象的实例化
private BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd) {
//1. 工厂方法(TODO)
//2. 无参构造器
Object beanInstance = getInstantiationStrategy().instantiate(mbd, beanName);
BeanWrapper bw = new BeanWrapperImpl(beanInstance);
bw.setConversionService(getConversionService());
return bw;
}
4.3 流程复盘
getBean
方法集查找和创建两大功能于一身,通过与 BeanDefinition
的密切配合,初步实现了控制反转的功能,即对象的创建是由容器掌握的。我们重新梳理一下获取 Bean 的整个流程,涉及三个类的操作,使用不同的颜色来区分。总的来说,可以分为以下几个步骤:
- 调用父类的
getSingleton
方法尝试获取单例的缓存。 - 如果单例不存在,进一步检查父容器是否存在。如果父容器存在,则调用
getBean
方法重复整个流程。 - 调用
getMergedBeanDefinition
方法,获取BeanDefinition
信息,前提是已经注册了BeanDefinition
。 - 调用父类的
getSingleton
方法,创建并注册单例。 - 创建 Bean 的流程实际上是通过
ObjectFactory
接口的回调完成的,最终调用AbstractAutowireCapableBeanFactory
的createBean
方法。 - 单例创建之后,调用父类的
addSingleton
方法将单例添加到缓存中,这样一来,下一次调用getBean
方法直接获取已缓存的单例即可。 - 此时得到的是一个可用的单例,进行必要的类型转换。
注:蓝色表示由
AbstractBeanFactory
执行,绿色表示由父类DefaultSingletonBeanRegistry
执行,黄色表示由子类AbstractAutowireCapableBeanFactory
执行。
5. 扩展
BeanFactory
实例创建之后,允许外界对其进行自定义处理,这一工作是由 BeanFactoryPostProcessor
接口完成的。postProcessBeanFactory
方法接收一个 ConfigurableBeanFactory
类型的参数,说明该方法侧重于容器的配置工作。
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableBeanFactory beanFactory) throws BeansException;
}
BeanDefinitionRegistryPostProcessor
作为子接口,postProcessBeanDefinitionRegistry
方法接收一个 BeanDefinitionRegistry
类型的参数,说明该方法负责注册 BeanDefinition
。
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws RuntimeException;
}
在实际使用中,ApplicationContext
持有一组 BeanFactoryPostProcessor
组件。我们关心的是 ConfigurationClassPostProcessor
实现类,该组件负责处理配置类,具体内容将在第三章 context 模块进行讨论。
6. 测试
在测试方法中,首先创建 DefaultListableBeanFactory
实例,然后注册了一个 BeanDefinition
,并指定创建的对象类型为 User
。然后调用 getBean
方法,Spring 容器会根据 BeanDefinition
来创建对象。
@Test
public void testGetBeanByBeanDefinition() {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition("bar", new RootBeanDefinition(User.class));
Object bar = factory.getBean("bar");
System.out.println("测试BeanDefinition: " + bar);
}
从测试结果中可以看到,User
是一个空对象,字段没有赋值。这是因为通过 BeanDefiniton
创建的对象,实际上是反射了默认的构造器,不能像普通对象那样通过有参构造器或 setter 方法来赋值。不过不用担心,创建的实例被包装成了一个 BeanWrapper
对象,这意味着后续可以通过属性访问的方式来赋值。
测试BeanDefinition: User{name='null', age=0}
7. 总结
本节介绍了创建单例的主流方式,即通过 BeanDefinition
实现自动创建对象的流程。涵盖了三个知识点。一是 BeanDefinition
的定义,该类描述了创建对象所需的各种信息,其中 RootBeanDefinition
作为创建对象的标准依据,AnnotatedBeanDefinition
接口表示通过注解的方式创建对象。
二是 BeanDefinition
的管理,BeanFactory
将 BeanDefinition
分为两级缓存。二级缓存可以存储 BeanDefinition
的各种实现类,是 BeanDefinition
注册到 Spring 容器中的临时存储区域。一级缓存只存储 RootBeanDefinition
,也就是说 BeanDefinition
的实现类都要转换成 RootBeanDefinition
才能使用。
三是创建对象的流程,可以分为三步。首先尝试从缓存中查找单例,如不存在转入创建流程。然后获取对应的 RootBeanDefinition
,作为创建对象的依据。接下来通过 InstantiationStrategy
接口完成对象的实例化工作。
8. 项目信息
新增修改一览,新增(13),修改(7)。
beans
└─ src
├─ main
│ └─ java
│ └─ cn.stimd.spring.beans
│ └─ factory
│ ├─ annotation
│ │ ├─ AnnotatedBeanDefinition.java (+)
│ │ └─ AnnotatedGenericBeanDefinition.java (+)
│ ├─ config
│ │ ├─ BeanDefinition.java (+)
│ │ ├─ BeanFactoryPostProcessor.java (+)
│ │ └─ ConfigurableBeanFactory.java (*)
│ ├─ support
│ │ ├─ AbstractAutowireCapableBeanFactory.java (*)
│ │ ├─ AbstractBeanDefinition.java (+)
│ │ ├─ AbstractBeanFactory.java (*)
│ │ ├─ BeanDefinitionRegistry.java (+)
│ │ ├─ BeanDefinitionRegistryPostProcessor.java (+)
│ │ ├─ DefaultListableBeanFactory.java (*)
│ │ ├─ DefaultSingletonBeanRegistry.java (*)
│ │ ├─ GenericBeanDefinition.java (+)
│ │ ├─ InstantiationStrategy.java (+)
│ │ ├─ RootBeanDefinition.java (+)
│ │ └─ SimpleInstantiationStrategy.java (+)
│ ├─ BeanFactory.java (*)
│ ├─ BeanFactoryUtils.java (+)
│ └─ ObjectFactory.java (+)
└─ test
└─ java
└─ beans
└─ factory
└─ FactoryTest.java (*)
注:+号表示新增、*表示修改
-
项目地址:https://gitee.com/stimd/spring-wheel
-
本节分支:https://gitee.com/stimd/spring-wheel/tree/chapter1-5
注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。
关注公众号【Java编程探微】,查看更多精彩内容。