Spring隐藏技能:FactoryBean的“&“魔法与单例缓存黑科技!

215 阅读3分钟

🔗 关联阅读:推荐先阅读 → 《BeanFactory与FactoryBean:Spring Boot中的核心概念解析》

在Spring框架的底层设计中,FactoryBean是一个充满技巧性的接口,它隐藏着许多开发者容易忽视的重要细节。今天我们就来深入剖析它的核心秘密!

一、FactoryBean的双重身份:工厂与产品

FactoryBean的本质:它是一个能生产其他Bean的工厂Bean。与普通Bean不同,它实现了FactoryBean<T>接口,在Spring容器中具有双重身份:

public interface FactoryBean<T> {
    T getObject() throws Exception;   // 生产实际需要的Bean
    Class<?> getObjectType();         // 返回产品类型
    boolean isSingleton();            // 决定产品是否单例
}

二、&符的魔法:获取工厂与产品的区别

假设我们定义了如下FactoryBean:

@Component("myFactoryBean")
public class MyFactoryBean implements FactoryBean<MyProduct> {
    @Override
    public MyProduct getObject() {
        return new MyProduct(); // 创建产品实例
    }
    
    @Override
    public Class<?> getObjectType() {
        return MyProduct.class;
    }
}

关键区别对比表

获取方式实际获取的对象Spring处理逻辑
getBean("myFactoryBean")MyProduct实例调用factoryBean.getObject()
getBean("&myFactoryBean")MyFactoryBean实例返回FactoryBean本身

代码验证:

// 获取产品对象(自动调用getObject())
MyProduct product = (MyProduct) context.getBean("myFactoryBean"); 

// 获取工厂对象本身(需要&前缀)
MyFactoryBean factory = (MyFactoryBean) context.getBean("&myFactoryBean"); 

三、对象加载过程与缓存机制深度解析

加载流程时序图

sequenceDiagram
    participant C as Spring容器
    participant FB as FactoryBean
    participant OC as 对象缓存
    
    C->>FB: getBean("myFactoryBean")
    alt 首次请求
        FB->>FB: 调用getObject()创建新对象
        FB->>OC: 存入一级缓存(singletonObjects)
    else 非首次请求且isSingleton=true
        C->>OC: 直接从一级缓存获取
    else isSingleton=false
        FB->>FB: 每次调用getObject()创建新对象
    end
    C-->>C: 返回产品对象

缓存机制的核心逻辑:

  1. 单例模式(isSingleton()返回true)

    • 第一次调用getBean()时:
      • 触发getObject()方法创建对象
      • 将产品对象存入一级缓存(singletonObjects)
    • 后续调用直接返回缓存对象
  2. 原型模式(isSingleton()返回false)

    • 每次调用getBean()都会执行getObject()
    • 创建全新对象实例
    • 不会进行任何缓存

源码佐证(AbstractBeanFactory):

protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName) {
    if (factory.isSingleton() && containsSingleton(beanName)) {
        synchronized (getSingletonMutex()) {
            // ✅ 从单例缓存获取
            Object object = this.factoryBeanObjectCache.get(beanName);
            if (object == null) {
                object = doGetObjectFromFactoryBean(factory);
                // ✅ 存入一级缓存
                this.factoryBeanObjectCache.put(beanName, object);
            }
            return object;
        }
    } else {
        // ❌ 原型模式直接创建新对象
        return doGetObjectFromFactoryBean(factory);
    }
}

private Object doGetObjectFromFactoryBean(FactoryBean<?> factory) {
    return factory.getObject(); // 最终调用getObject()
}

四、关键问题解答:缓存位置之谜

问题:FactoryBean创建的对象是否存入一级缓存?
答案:分两种情况!

  • 单例产品:存入专门为FactoryBean设计的factoryBeanObjectCache(本质是单例缓存的特例)
  • 原型产品:不缓存,每次创建新对象

📌 重要提示:Spring对FactoryBean创建的对象进行了特殊缓存处理,它们不会进入普通的单例缓存(singletonObjects),而是存储在独立的factoryBeanObjectCache中。

五、避坑指南:常见使用误区

  1. 混淆工厂与产品

    // 错误!获取的是产品对象而非工厂
    MyFactoryBean factory = context.getBean("myFactoryBean"); 
    
    // 正确!使用&前缀获取工厂
    MyFactoryBean factory = context.getBean("&myFactoryBean");
    
  2. 忽视作用域配置

    @Override
    public boolean isSingleton() {
        return false; // 必须显式声明作用域
    }
    

总结

FactoryBean的核心秘密可归纳为三点:

  1. &前缀魔法:区分获取工厂本身还是产品对象
  2. 缓存双轨制:单例产品特殊缓存,原型产品不缓存
  3. 作用域控制:通过isSingleton()决定对象生命周期

理解这些机制后,我们就能在Spring应用中游刃有余地使用FactoryBean实现灵活的对象创建逻辑,同时避免常见的陷阱。