likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 19

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> {
    getObject() throws Exception;
    Class<?> getObjectType();
    boolean isSingleton();
}

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

2.2 双重工厂方法

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

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

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<StringObject> factoryBeanObjectCache = new ConcurrentHashMap<>(16);
}

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

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

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 进一步获取目标对象。

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

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 (+)

注:+号表示新增、*表示修改

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


欢迎关注公众号【Java编程探微】,加群一起讨论。

原创不易,觉得内容不错请点赞收藏。

转载自:https://juejin.cn/post/7379960023407214604
评论
请登录