【重写SpringFramework】第一章beans模块:单例创建(chapter 1-5)

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 接口有三个实现类,其中 ConfigurationClassBeanDefinitionScannedGenericBeanDefinition 使用红色标识,表示它们不属于 beans 模块。Spring 使用这三个类提供了灵活且方便的创建 Bean 的方式,我们将在第三章 context 模块详细介绍。

2.2 AbstractBeanDefinition

AbstractBeanDefinition 持有一系列描述创建对象的属性,BeanDefinition 接口则定义了获取这些属性的方法。我们直接来看这些属性的作用,如下所示:

  • beanClass:作为最重要的属性,表示待创建对象的类型。该字段可能是 ClassString 类型,且最终会被转换为 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 分为两级缓存,其中 DefaultListableBeanFactorybeanDefinitionMap 字段是二级缓存,存储 BeanDefinition 接口的实现类。AbstractBeanFactorymergedBeanDefinitions 字段是一级缓存,且限定类型必须是 RootBeanDefinition

在这里插入图片描述

如图所示,先把 RootBeanDefinitionGenericBeanDefinitionAnnotatedBeanDefinition 接口的实现类等注册到二级缓存中。然后在执行创建流程时,对二级缓存中的 BeanDefinition 进行处理,转换成 RootBeanDefinition 并保存到一级缓存中。从这里可以看到,RootBeanDefinition 是创建对象的依据,其他实现类则是适用不同情况的权宜之计。

4. doGetBean方法详解

4.1 获取Bean的流程

AbstractBeanFactory 实现了 BeanFactory 接口的一系列 getBean 方法,它们最终都会调用 doGetBean 方法。我们先来从全局观察整个方法,大体可以分为三步。

  1. 单例已存在容器中,直接从缓存中获取。
  2. 如果单例不存在,需要通过 BeanDefinition 来创建对象,并注册到容器中。
  3. 拿到单例之后,还要检查是否与所需的类型一致,如果不一致需要进行类型转换。

第一步,首先尝试从缓存中获取 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;
}

第二步,如果单例不存在,那么就需要创建对象,然后加入缓存中。这一步骤可以分解为四个阶段,如下:

  1. 检查父容器是否存在,如果存在尝试从父容器中获取单例,也就是递归调用 doGetBean 方法。
  2. 获取对应的 BeanDefinition,调用 getMergedBeanDefinition 方法确保拿到的是 RootBeanDefinition 对象。
  3. 调用 getSingleton 方法,通过回调 ObjectFactory 匿名对象的 getObject 方法,间接调用 createBean 方法,完成对象的创建工作。
  4. 如果单例的类型是 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 方法可以划分为五个步骤,每一步都很重要,本章的后续内容主要是围绕这几步展开的。简单介绍如下:

  1. Bean 的实例化,也就是创建目标对象。
  2. 在实现依赖注入功能时,循环依赖是一件棘手的事情,需要进行一定的处理。
  3. 填充对象,主要是对字段进行赋值,依赖注入的功能就是在这一步实现的。
  4. 此时已经得到了一个相对完整的实例,初始化的作用主要是提供了一定的扩展,允许用户对实例进行定制化,完成某些自定义的处理逻辑。
  5. 当 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 的整个流程,涉及三个类的操作,使用不同的颜色来区分。总的来说,可以分为以下几个步骤:

  1. 调用父类的 getSingleton 方法尝试获取单例的缓存。
  2. 如果单例不存在,进一步检查父容器是否存在。如果父容器存在,则调用 getBean 方法重复整个流程。
  3. 调用 getMergedBeanDefinition 方法,获取 BeanDefinition 信息,前提是已经注册了 BeanDefinition
  4. 调用父类的 getSingleton 方法,创建并注册单例。
  5. 创建 Bean 的流程实际上是通过 ObjectFactory 接口的回调完成的,最终调用 AbstractAutowireCapableBeanFactorycreateBean 方法。
  6. 单例创建之后,调用父类的 addSingleton 方法将单例添加到缓存中,这样一来,下一次调用 getBean 方法直接获取已缓存的单例即可。
  7. 此时得到的是一个可用的单例,进行必要的类型转换。

在这里插入图片描述

注:蓝色表示由 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 的管理,BeanFactoryBeanDefinition 分为两级缓存。二级缓存可以存储 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编程探微】,查看更多精彩内容。
在这里插入图片描述

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值