【重写SpringFramework】第一章beans模块:FactoryBean(chapter 1-11)

1. 前言

Spring 容器创建对象主要是通过 BeanDefinition 完成的,先前我们设置 beanClass 属性,然后通过反射无参构造器的方式创建对象。此时得到的是一个空对象,还需要通过依赖注入、初始化等操作构建一个可用的完整对象。

本节我们来讨论另外一种情形,假如有一个对象的构建过程非常复杂,并且这个对象属于第三方框架。一般来说,第三方框架是独立的,依赖注入、初始化等机制无法使用,我们需要一种更加灵活的创建对象的方式。Spring 的解决思路是,既然复杂对象的创建细节只有第三方框架最熟悉,那么干脆让第三方框架来创建对象,这样一来最大限度地保证了灵活性。

Spring 容器将创建对象的权力从类的手里收回,被称为控制反转。现在为了寻求更大的灵活性,又将创建对象的权力下放给类。这种控制反转的再反转产生了一个特殊的现象,称之为 IOC 机制的反模式(anti-pattern)。其实仔细思考一下,创建的对象还是由 Spring 容器进行管理,本质上并没有破坏 IOC 机制。说到底,编程是一门妥协的艺术,没有一成不变的原则。IOC 机制的核心是对资源的管理,通过让渡一部分非核心权力,得到的是更大的灵活性。

2. FactoryBean

2.1 概述

FactoryBean 接口本质上是一个包装对象,泛型参数 T 表示被包装的目标对象。FactoryBean 接口定义了工厂 Bean 的相关方法,如下所示:

  • getObject 方法是核心的工厂方法,作用是创建目标对象。

  • getObjectType 方法的作用是获取目标对象的类型。

  • isSingleton 方法的作用是判断目标对象是否为单例,FactoryBean 本身会被当做单例注册到 Spring 容器,但目标对象则不一定。我们只关心单例,默认为 true。

public interface FactoryBean<T> {
    T getObject() throws Exception;
    Class<?> getObjectType();
    boolean isSingleton();
}

一开始接触 FactoryBean,可能会和 BeanFactory 混淆在一起。FactoryBean 的重点在于它首先是一个 Bean,前缀 Factory 表明它起到一个工厂的作用。BeanFactory 则强调它是一个工厂,作用是创建和管理 Bean。FactoryBean 既然是一个 Bean,那么就能够被 Spring 容器管理,只是稍微有点特殊罢了。

2.2 双重工厂方法

BeanFactory 使用的是工厂模式,而 FactoryBean 本身则是一个工厂对象,这样就构成了双重的工厂方法。如下图所示,首先要经过外层的工厂方法 getBean,然后是内层的工厂方法 getObject,然后才能得到目标对象。

在这里插入图片描述

2.3 FactoryBean 的管理

普通对象在 Spring 容器中仅存储单例本身,对于 FactoryBean 来说,既要存储工厂 Bean,也要存储目标对象。DefaultSingletonBeanRegistry 类存放的是工厂 Bean(创建完成后存放在一级缓存),而真正的目标对象则存储在 FactoryBeanRegistrySupport 的缓存中。

之前我们在介绍 BeanFactory 接口的继承体系时提到过这个类,当时没有展开讲。这个类的功能比较单一,就是存储工厂 Bean 创建的目标对象。factoryBeanObjectCache 字段负责存储通过 FactoryBean 创建的对象,这种方式并不常用,因此初始容量为 16。

public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry {
    //存储FactoryBean创建的目标Bean
    private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>(16);
}

Spring 容器分开存储工厂 Bean 和目标对象,我们通过常规的对象名得到的是目标对象,如果想获得工厂 Bean,需要在对象名前添加前缀。这个前缀是 BeanFactory 接口的 FACTORY_BEAN_PREFIX 字段,其值为 &。以 Foo 对象为例,目标对象的名称为 foo,工厂对象的名称为 &foo。如下图所示,调用 getBean 方法的参数如果是 &foo,得到的是 FooFactoryBean 对象。如果是 foo,得到的是 Foo 对象。

在这里插入图片描述

3. 代码实现

3.1 主流程

FactoryBean 和普通 Bean 的创建流程是一样的,两者的区别主要在 doGetBean 方法。先来看 namebeanName 两个参数,name 是外部传入的,可能包含 & 前缀。beanName 是经过处理的正常对象名,不包含 & 前缀。简单来说,doGetBean 方法要么执行查询缓存的操作,要么执行创建对象的操作,分别对这两步进行分析。

第一步,尝试从缓存中查找实例。需要注意的是,getSingleton 方法传入的是正常的对象名。此时 sharedInstance 变量指向的是 FactoryBean 包装对象,如果我们想得到目标对象,还要进一步调用 getObjectForBeanInstance 方法。

第二步,创建对象,并添加到缓存中。createBean 方法需要注意两点,一是创建对象的类型是 FactoryBean,二是传入的是 beanName 参数,这说明 FactoryBean 包装对象的名称是不带前缀的 foo。同样地,进一步调用 getObjectForBeanInstance 方法来获取目标对象。

综上所述,无论是查询缓存还是创建对象,首先得到的都是 FactoryBean 对象,然后才是目标对象,因此重点在于 getObjectForBeanInstance 方法的实现。

//所属类[cn.stimd.spring.beans.factory.support.AbstractBeanFactory]
//获取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) {
        //此时拿到的是FactoryBean,还需要获取目标对象(mbd参数为空,说明是检索操作)
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
    //2. 缓存中不存在,则通过BeanDefinition来创建对象,并加入缓存
    else {
        //2.1 检查父容器中是否存在Bean(略)

        //2.2 获取BeanDefinition
        final RootBeanDefinition mbd = getMergedBeanDefinition(beanName);
        //2.3 创建单例Bean并注册到容器中
        sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
        	@Override
            public Object getObject() throws BeansException {
                return createBean(beanName, mbd);
            }
        });
        //2.4 如果是FactoryBean,则进一步获取目标Bean(mdb参数存在,说明是创建操作)
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }
    //3. 类型转换(略)
    return (T) bean;
}

3.2 FactoryBean 的处理

getObjectForBeanInstance 方法的名字可以看出,是要从实例(bean)中获取对象(object),这里的实例主要是指 FactoryBean,对象是指被包装的目标对象。该方法的特殊性在于可以处理多种情况,具体情况则由参数决定的,我们先来看参数的含义。

  • beanInstance:表示传入的对象,可能是 FactoryBean,也可能是普通对象
  • name:表示查询的对象名(调用getBean 方法传入的参数),可能是 &foo,也可能是 foo
  • beanName:实际的对象名,固定为 foo
  • mbd:如果为空说明是检索操作,否则是创建操作
//所属类[cn.stimd.spring.beans.factory.support.AbstractBeanFactory]
//从Bean的实例中获取对象
protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {
    //1. 不允许普通Bean的name以&开头
    if(name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX) && !(beanInstance instanceof FactoryBean)){
        throw new BeansException("beanName[" + name + "]不是FactoryBean,实际类型为" + beanInstance.getClass() );
    }

    /*
     * 2. 只有当beanInstance是FactoryBean类型,且name也是foo(说明需要的是被FactoryBean包装的对象),才可以执行后续流程
     *
     * 左侧表达式说明是一个普通Bean,隐含条件“name不以&开头”已经在Step-1被过滤掉了。
     * 右侧表达式说明是一个FactoryBean,隐含条件“不是FactoryBean的实例”在左侧被过滤掉了
     */
    if (!(beanInstance instanceof FactoryBean) || name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
        return beanInstance;
    }

    //3. 使用工厂方法的方式获取Bean
    Object object = null;
    //mbd为空,说明是检索Bean的操作,只查询缓存即可
    if (mbd == null) {
        object = getCachedObjectForFactoryBean(beanName);
    }

    //4. 缓存中没找到,则创建目标Bean
    if(object == null){
        FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
        object = getObjectFromFactoryBean(factory, beanName);
    }
    return object;
}

getObjectForBeanInstance 方法可以分为四步,前两步是前置检查。由于参数 beanInstancename 都有两种可能,两两组合一共有四种情况,需要排除掉三种情况。直接看代码不太好理解,我们将四种情况排列出来,如下所示:

  1. beanInstance 是一个普通对象,name&foo,不允许的情况,第一步报错。
  2. beanInstance 是一个普通对象,namefoo,说明查找的就是普通对象,第二步的左侧表达式返回。
  3. beanInstance 是一个 FactoryBeanname&foo,说明查找的就是一个 FactoryBean,第二步的右侧表达式返回。
  4. beanInstance 是一个 FactoryBeannamefoo,说明查找的是一个被包装的对象,执行后续流程。

通过前两步的检查,此时的目标已经明确,就是要从 FactoryBean 中获取目标对象。因此第三步尝试从缓存中查找,如果不存在,进入第四步创建目标对象的流程。

3.3 创建目标对象

getObjectFromFactoryBean 方法是由父类 FactoryBeanRegistrySupport 实现的,首先检查 FactoryBean 的实例是否存在。如果 FactoryBean 的作用域不是单例,或者 FactoryBean 没有被 Spring 容器托管,那么直接创建对象即可。非单例的情况不是我们所关心的,接下来进入 FactoryBean 是单例的处理流程,分为三步:

  1. 尝试从缓存中获取目标对象(第一次访问不存在)
  2. 如果缓存中不存在,则调用 FactoryBean 接口的 getObject 方法创建目标对象
  3. 将目标对象加入缓存中(factoryBeanObjectCache 字段)
//所属类[cn.stimd.spring.beans.factory.support.FactoryBeanRegistrySupport]
//从FactoryBean中获取对象,如不存在则创建
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName) {
    if (factory.isSingleton() && containsSingleton(beanName)) {
        synchronized (getSingletonMutex()) {
            //1) 尝试从缓存中获取目标对象
            Object object = this.factoryBeanObjectCache.get(beanName);
            if (object == null) {
                //2) 调用getObject方法创建目标对象
                object = doGetObjectFromFactoryBean(factory, beanName);
                //3) 将目标对象加入到缓存
                this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
            }
            return (object != NULL_OBJECT ? object : null);
        }
    }
    //非单例Bean,无需缓存,直接创建新对象
    return doGetObjectFromFactoryBean(factory, beanName);
}

3.4 设计思路

我们发现,Bean 的销毁和 FactoryBean 的设计上有相似之处,都是将包装对象和目标对象分开存储。一般来说,使用带前缀的 beanName 获取 FactoryBean,使用普通 beanName 获取目标对象。但是经过上述代码分析,FactoryBean 和目标对象都是使用普通 beanName 存储的,区别在于存储的位置不同。事实上,带前缀 beanName 的作用并不是直接获取对象,而是判断是否要根据 FactoryBean 进一步获取目标对象。

在这里插入图片描述

4. 测试

先定义一个测试类 UserFactoryBean,实现了 FactoryBean 接口,通过 getObject 方法最终得到一个 User 对象。

//测试类
public class UserFactoryBean implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
        return new User("通过工厂创建", 22);
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

在测试方法中,首先创建 BeanDefinition 对象,注意类型是 UserFacotyBean,然后注册到 Spring 容器中。接下来是获取对象,通过 &user 拿到的是 UserFactoryBean 对象,通过 user 拿到的才是包装在内部的 User 对象。

//测试方法
@Test
public void testFactoryBean() throws Exception {
    RootBeanDefinition definition = new RootBeanDefinition();
    definition.setBeanClass(UserFactoryBean.class);
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    factory.registerBeanDefinition("user", definition);

    UserFactoryBean factoryBean = (UserFactoryBean) factory.getBean("&user");
    User user = factory.getBean("user", User.class);
    System.out.println("工厂Bean:" + factoryBean + ", 创建的实例:" + factoryBean.getObject());
    System.out.println("直接获取普通Bean: " + user);
}

从测试结果可以看到,&user 指向的是包装对象 UserFactoryBeanuser 指向的是目标对象 User

获取FactoryBean:beans.factory.UserFactoryBean@3c5a99da
获取目标对象: User{name='通过工厂创建', age=22}

5. 总结

本节我们讨论了 FactoryBean,这是一种通过工厂方法来创建对象的方式。虽然 Spring 容器主要通过反射无参构造器的方式创建对象,但对于一些复杂对象来说,需要更加灵活的创建方式。FactoryBean 接口允许第三方自行完成对象的创建工作,但创建的对象仍然交由 Spring 容器管理。

FactoryBean 接口的实现类实际上是一个包装类,外层的 FactoryBean 是工厂类,内层的是工厂方法创建的目标对象。Spring 容器将工厂 Bean 和目标对象分别存储,当我们创建或获取目标对象,实际上是分两步进行的:首先完成对工厂 Bean 的处理,然后处理目标对象。

FactoryBean 的本质是模式化与反模式化的问题,Spring 容器建立在 IOC 机制之上,按理说应该由容器来创建对象,这是模式化,但是将创建对象的权力交还给类则是反模式的。Spring 容器首先需要立足于既有流程,在实现基本功能的基础上,还要为外部提供一定的可扩展性。

6. 项目信息

新增修改一览,新增(2),修改(4)。

beans
└─ src
   ├─ main
   │  └─ java
   │     └─ cn.stimd.spring.beans
   │        └─ factory
   │           ├─ support
   │           │  ├─ AbstractAutowireCapableBeanFactory.java (*)
   │           │  ├─ AbstractBeanFactory.java (*)
   │           │  └─ FactoryBeanRegistrySupport.java (*)
   │           └─ FactoryBean.java (+)
   └─ test
      └─ java
         └─ beans
            └─ factory
               ├─ FactoryTest.java (*)
               └─ UserFactoryBean.java (+)

注:+号表示新增、*表示修改
  • 项目地址:https://gitee.com/stimd/spring-wheel

  • 本节分支:https://gitee.com/stimd/spring-wheel/tree/chapter1-11

注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。


关注公众号【Java编程探微】,加群一起讨论。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值