引言

在 Spring 框架中,存在两个名称极为相似的接口:BeanFactoryFactoryBean。由于命名接近,开发者在实际使用中容易产生混淆。本文将深入解析两者的定义、源码、使用场景及核心区别。

1. BeanFactory

BeanFactory 是 Spring IoC(Inversion of Control,控制反转)容器的顶层接口,定义了工厂规范。它提供了管理 Bean 的通用方法,如 getBean()containsBean() 等。Spring 容器本质上都是该接口的具体实现,常见的实现类包括:

  • DefaultListableBeanFactory
  • XmlBeanFactory(已废弃)
  • ApplicationContext(继承自 ListableBeanFactory,间接实现 BeanFactory

这些实现类从不同维度提供了功能扩展,构成了 Spring 容器的核心基础。

1.1 接口源码

public interface BeanFactory {

    // 对 FactoryBean 的转义定义。如果使用 bean 的名字检索 FactoryBean,得到的是工厂生成的对象;
    // 如果需要得到工厂本身,需要加上此转义前缀。
    String FACTORY_BEAN_PREFIX = "&";

    // 根据 bean 的名字,获取在 IOC 容器中的 bean 实例
    Object getBean(String name) throws BeansException;

    // 根据 bean 的名字和 Class 类型来获取 bean 实例,增加了类型安全验证机制
    <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;

    Object getBean(String name, Object... args) throws BeansException;

    <T> T getBean(Class<T> requiredType) throws BeansException;

    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

    // 检索 IOC 容器中是否包含指定名字的 bean
    boolean containsBean(String name);

    // 根据 bean 名字得到 bean 实例,并同时判断这个 bean 是不是单例
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

    // 得到 bean 实例的 Class 类型
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    // 得到 bean 的别名,如果根据别名检索,那么其原名也会被检索出来
    String[] getAliases(String name);
}

1.2 使用场景

  • 从 IoC 容器中获取 Bean(支持按名称或按类型)。
  • 检索 IoC 容器中是否包含指定的 Bean。
  • 判断 Bean 的作用域(是否为单例)。

2. FactoryBean

FactoryBean 本身是一个 Bean,但它不仅仅是一个普通的 Bean。它是一个能生产或修饰对象生成的工厂 Bean,类似于设计模式中的工厂模式装饰器模式。它能在需要的时候生产一个对象,且不仅限于自身,能返回任何 Bean 的实例。

2.1 接口源码

public interface FactoryBean<T> {

    // 从工厂中获取 bean 实例
    @Nullable
    T getObject() throws Exception;

    // 获取 Bean 工厂创建的对象的类型
    @Nullable
    Class<?> getObjectType();

    // Bean 工厂创建的对象是否是单例模式
    default boolean isSingleton() {
        return true;
    }
}

从接口定义可以看出,FactoryBean 体现的是工厂的职责。如果一个 Bean A 实现了 FactoryBean 接口,那么 A 就变成了一个工厂。

  • 根据 A 的名称获取到的实际上是工厂调用 getObject() 返回的对象,而不是 A 本身。
  • 如果要获取工厂 A 自身的实例,需要在名称前面加上 & 符号(即 BeanFactory.FACTORY_BEAN_PREFIX)。

具体获取规则如下:

  • getBean("name"):返回工厂生产的实例(getObject() 的返回值)。
  • getBean("&name"):返回工厂本身的实例(Bean A 自身)。

通常情况下,Bean 无须自己实现工厂模式,Spring 容器担任了工厂的角色;但在少数情况下,容器中的 Bean 本身就是工厂,作用是产生其他 Bean 实例。由工厂 Bean 产生的其他 Bean 实例,不再由 Spring 容器直接产生,因此与普通 Bean 的配置不同,不再需要提供 class 元素。

2.2 使用示例

首先定义一个 Bean 实现 FactoryBean 接口:

@Component
public class MyBean implements FactoryBean {
    private String message;

    public MyBean() {
        this.message = "通过构造方法初始化实例";
    }

    @Override
    public Object getObject() throws Exception {
        // 这里并不一定要返回 MyBean 自身的实例,可以是其他任何对象的实例。
        // 如 return new Student()...
        return new MyBean("通过 FactoryBean.getObject() 创建实例");
    }

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

    public String getMessage() {
        return message;
    }
}

MyBean 实现了 FactoryBean 接口的方法。getObject() 可以返回任何对象的实例,这里测试返回 MyBean 自身实例,且在返回前给 message 字段赋值。同时在构造方法中也为 message 赋值。

测试代码中,先通过名称获取 Bean 实例,打印 message 的内容;再通过 & + 名称获取实例并打印 message 内容:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class FactoryBeanTest {
    @Autowired
    private ApplicationContext context;

    @Test
    public void test() {
        MyBean myBean1 = (MyBean) context.getBean("myBean");
        System.out.println("myBean1 = " + myBean1.getMessage());
        
        MyBean myBean2 = (MyBean) context.getBean("&myBean");
        System.out.println("myBean2 = " + myBean2.getMessage());
        
        System.out.println("myBean1.equals(myBean2) = " + myBean1.equals(myBean2));
    }
}

控制台输出结果:

myBean1 = 通过 FactoryBean.getObject() 初始化实例
myBean2 = 通过构造方法初始化实例
myBean1.equals(myBean2) = false

2.3 典型应用场景

为什么需要 FactoryBean?它在 Spring 中最为典型的应用就是用来创建 AOP 的代理对象

AOP 实际上是 Spring 在运行时创建了一个代理对象。也就是说,这个对象是在运行时创建的,而不是一开始就定义好的,这很符合工厂方法模式。更形象地说,AOP 代理对象通过 Java 的反射机制,在运行时创建了一个代理对象,在代理对象的目标方法中根据业务要求织入了相应的方法。这个对象在 Spring 中就是——ProxyFactoryBean

因此,FactoryBean 为我们实例化 Bean 提供了一个更为灵活的方式,我们可以通过 FactoryBean 创建出更为复杂的 Bean 实例。

3. 核心区别

特性BeanFactoryFactoryBean
本质Spring 容器的顶层接口,负责管理 Bean本质上是一个 Bean,由容器管理
角色容器本身,负责生产和管理所有 Bean用户自定义的工厂,负责生产特定对象
获取方式直接作为容器接口使用需实现接口,通过 getObject() 获取对象
转义符号定义了 & 前缀用于区分工厂本身获取工厂本身时需加 & 前缀
关系管理 FactoryBeanBeanFactory 管理

简而言之:

  • 两者都涉及工厂模式,但 FactoryBean 本质上还是一个 Bean,也归 BeanFactory 管理。
  • BeanFactory 是 Spring 容器的顶层接口,FactoryBean 更类似于用户自定义的工厂接口。

总结

BeanFactoryFactoryBean 的区别确实容易混淆,死记硬背是不行的,最好还是从源码层面,置于 Spring 的环境中去理解。

参考:

说明:文中提到的 XmlBeanFactory 实现类在 Spring 3.1 版本中已被标记为废弃(Deprecated),并在 Spring 4.0 中移除,建议在新项目中直接使用 ApplicationContext 相关实现。