【重写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
方法。先来看 name
和 beanName
两个参数,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
方法可以分为四步,前两步是前置检查。由于参数 beanInstance
和 name
都有两种可能,两两组合一共有四种情况,需要排除掉三种情况。直接看代码不太好理解,我们将四种情况排列出来,如下所示:
beanInstance
是一个普通对象,name
是&foo
,不允许的情况,第一步报错。beanInstance
是一个普通对象,name
是foo
,说明查找的就是普通对象,第二步的左侧表达式返回。beanInstance
是一个FactoryBean
,name
是&foo
,说明查找的就是一个FactoryBean
,第二步的右侧表达式返回。beanInstance
是一个FactoryBean
,name
是foo
,说明查找的是一个被包装的对象,执行后续流程。
通过前两步的检查,此时的目标已经明确,就是要从 FactoryBean
中获取目标对象。因此第三步尝试从缓存中查找,如果不存在,进入第四步创建目标对象的流程。
3.3 创建目标对象
getObjectFromFactoryBean
方法是由父类 FactoryBeanRegistrySupport
实现的,首先检查 FactoryBean
的实例是否存在。如果 FactoryBean
的作用域不是单例,或者 FactoryBean
没有被 Spring 容器托管,那么直接创建对象即可。非单例的情况不是我们所关心的,接下来进入 FactoryBean
是单例的处理流程,分为三步:
- 尝试从缓存中获取目标对象(第一次访问不存在)
- 如果缓存中不存在,则调用
FactoryBean
接口的getObject
方法创建目标对象 - 将目标对象加入缓存中(
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
指向的是包装对象 UserFactoryBean
,user
指向的是目标对象 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 (+)
注:+号表示新增、*表示修改
- 项目地址:gitee.com/stimd/sprin…
- 本节分支:gitee.com/stimd/sprin…
注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。
欢迎关注公众号【Java编程探微】,加群一起讨论。
原创不易,觉得内容不错请点赞收藏。
转载自:https://juejin.cn/post/7379960023407214604