
几个月前我在排查一个老系统启动缓慢的问题连续在日志里看到 BeanFactory、FactoryBean、ObjectFactory 三个名字轮番出场。那会儿我脑子里只有一个想法这三个东西都带 Factory到底谁是谁相信不少人和我一样每次看完源码分析觉得自己懂了回头写代码遇到getBean(xxx)或者ObjectFactoryXxx这种写法又觉得自己根本没懂。这种反复被绕晕的感觉就很像推西西弗斯那块石头——快推到山顶了手一滑又滚回山脚。这篇文章就是要把这块石头彻底按在山顶把 BeanFactory、FactoryBean、ObjectFactory 分别是什么、解决什么问题、实际代码里怎么用、三者怎么协作一次性讲明白。1. 为什么这块石头总在滚动三个名字背后的角色错位1.1 只看名字以为是“一家人”三个名字里都有 FactoryBeanFactory 还带 BeanFactoryBean 又带 BeanObjectFactory 再带 Factory。单看命名正常人都会觉得这是三兄弟要么是继承关系要么功能差不多。实际上它们完全不在同一个抽象层次上。先给一张定位表后面再逐个拆名字字面含义真实角色一句话记忆BeanFactoryBean 工厂IoC 容器的根接口容器体系的基石它就是容器本身FactoryBean工厂 Bean一种特殊的 Bean用来生产另一个对象它是“被容器管理的生产者”ObjectFactory对象工厂一个延迟获取对象的接口本身不是容器的一部分它是一张“取货单”这张表看起来简单但能解决大部分初期的困惑。后面所有细节都可以从这张表延伸出来。1.2 三个“Factory”属于不同的抽象层次我把这三个概念在心里分成了三层这样再也不会混BeanFactory 属于容器基础设施层。它定义了 IoC 容器最基本的契约怎么按名字拿对象、怎么判断对象是不是单例、有没有某个 Bean 的定义。Spring 体系里所有高级容器最终都是在它之上做的扩展。FactoryBean 属于Bean 定义层。它也是容器里的一个 Bean会被 Spring 当普通 Bean 创建和管理。但它特殊在容器发现一个 Bean 实现了 FactoryBean 接口后默认不会把工厂本身返回给你而是调用它的getObject()方法把方法返回的产品对象交给你。ObjectFactory 属于使用方延迟层。它既不管容器也不管 Bean 怎么创建。它就是把“获取某个 Bean 的动作”封装成一个对象让你可以推迟到真正需要的时候再执行getObject()。用一个生活里的类比收一下BeanFactory 是整个餐厅管食材、管后厨、管上菜流程FactoryBean 是餐厅里那位分子料理大厨系统里登记的是“大厨”但你最终吃到的是他做出来的菜ObjectFactory 是一张“凭此券可领一份甜品”的取货单你拿着单子的时候甜品还没做哪天想吃了才拿去后厨兑现。1.3 一个常见的错误记忆方式我见过不少人把“FactoryBean 是 BeanFactory 的实现类”当成结论背下来这是完全错误的。FactoryBean 和 BeanFactory 在接口层面没有任何继承关系它们是两个独立的接口只是都活在同一个容器体系内。真正的关系很简单BeanFactory 拿着 FactoryBean 定义发现这个 Bean 的类实现了 FactoryBean 接口后会调用它的getObject()来产出实际对象。所以 FactoryBean 更像是 BeanFactory 的“手下一员”而不是“上级”。这种角色错位就是这块石头反复滚回去的最大原因。2. BeanFactory 不只是“会 getBean 的工具”容器体系的根节点2.1 接口设计getBean 是灵魂但不是全部很多人提到 BeanFactory 就想到getBean(xxx)确实这是最常用的方法但整个接口设计的野心比这大得多。下面列一下接口里的核心方法public interface BeanFactory { Object getBean(String name) throws BeansException; T T getBean(String name, ClassT requiredType) throws BeansException; T T getBean(ClassT requiredType) throws BeansException; boolean containsBean(String name); boolean isSingleton(String name) throws NoSuchBeanDefinitionException; boolean isPrototype(String name) throws NoSuchBeanDefinitionException; String[] getAliases(String name); }getBean定义了“按名字或类型获取对象”的能力containsBean定义了“判断容器里是否存在某个定义”的能力isSingleton和isPrototype定义了“查询作用域”的能力getAliases则是别名机制。这些方法合在一起其实描述了一个 IoC 容器最基础的契约注册、查找、判断、管理生命周期。所谓控制反转落到接口层面就是这一组抽象能力。2.2 ApplicationContext 和 BeanFactory 的关系实际开发中我们用到的大多是 ApplicationContext比如常见的ClassPathXmlApplicationContext、AnnotationConfigApplicationContext。很多人因此觉得 ApplicationContext 才是容器BeanFactory 只是个概念。实际上 ApplicationContext 接口本身继承了 BeanFactory 子接口并且内部真正干事的是一个DefaultListableBeanFactory实例。换句话说你手里拿的 ApplicationContext其实就是一个“强化版 BeanFactory”。它额外承担了事件发布、国际化、资源加载这样的企业级功能但 IoC 最核心的 Bean 注册和获取还是委托给底层 BeanFactory 完成的。看源码的时候你会发现几乎所有的getBean调用最终都会落到DefaultSingletonBeanRegistry或者AbstractBeanFactory里的方法这正是 BeanFactory 体系真正发力的地方。2.3 真正直接操作 BeanFactory 的场景你可能觉得自己一辈子不会直接写DefaultListableBeanFactory但其实有些场景绕不开单元测试里想临时构建一个容器不启动整个 Spring 上下文。做框架扩展时需要手动向容器注册 Bean 定义或者单例对象。脱离 Spring Boot在极简环境下手工管理 Bean。我给大家看一段典型的直接操作代码public void manualContainerDemo() { DefaultListableBeanFactory factory new DefaultListableBeanFactory(); // 手动注册单例 UserService userService new UserService(); factory.registerSingleton(userService, userService); // 从容器获取 UserService fetched factory.getBean(userService, UserService.class); // 判断作用域 boolean isSingleton factory.isSingleton(userService); System.out.println(isSingleton); // true }这段代码非常直观地展示了 BeanFactory 最底层的用法注册、获取、判断。平时我们用 ApplicationContext 做得更多的事本质上都是在这个基础能力上面加糖。3. FactoryBean挂着 Bean 的名字干着工厂的活3.1 接口契约与“”前缀FactoryBean 的接口非常小public interface FactoryBeanT { T getObject() throws Exception; Class? getObjectType(); default boolean isSingleton() { return true; } }三个方法分别定义了“怎么生产产品”“产品类型是什么”“产品是不是单例”。这里要注意isSingleton()返回的是getObject()产物在容器里的缓存方式而不是 FactoryBean 实例本身的作用域。最让人迷惑的地方是获取方式。假设你注册了一个 name 为complexConnection的 FactoryBean那么// 返回的是 getObject() 的产品对象 ComplexConnection conn context.getBean(complexConnection, ComplexConnection.class); // 返回的是 FactoryBean 本身 ComplexConnectionFactoryBean factory (ComplexConnectionFactoryBean) context.getBean(complexConnection);没错Spring 约定用前缀来区分“要产品”和“要工厂”。这个设计有点像你去前台说“找负责人”前台以为你要找项目负责人其实你要找的是负责招聘的 HR——同一份编号不同前缀拿到的完全不是同一个人。3.2 实战例子自己写一个 FactoryBean假设我们要向容器注册一个初始化流程特别重的连接对象初始化不仅需要网络地址、超时时间还可能需要做资源预检、写入诊断日志。这时候写一个 FactoryBean 把初始化流程收拢起来是标准做法。public class ComplexConnectionFactoryBean implements FactoryBeanComplexConnection { private String endpoint; private int timeout; public void setEndpoint(String endpoint) { this.endpoint endpoint; } public void setTimeout(int timeout) { this.timeout timeout; } Override public ComplexConnection getObject() throws Exception { ComplexConnection connection new ComplexConnection(); connection.setEndpoint(endpoint); connection.setTimeout(timeout); connection.init(); // 这里可能要做资源预检日志校验等 return connection; } Override public Class? getObjectType() { return ComplexConnection.class; } }然后用配置把它注册进容器Bean public ComplexConnectionFactoryBean complexConnectionFactoryBean() { ComplexConnectionFactoryBean factory new ComplexConnectionFactoryBean(); factory.setEndpoint(jdbc://some-db); factory.setTimeout(3000); return factory; }这时候使用方直接注入ComplexConnection即可完全不用知道它的初始化被封装在了一个 FactoryBean 里。这就是 FactoryBean 的封装价值把 FactoryBean 实例本身注册成 Bean但对外暴露的是更语义化的产品类型。3.3 Spring 为什么要设计这么个东西设计 FactoryBean 的根本原因是有些 Bean 的创建过程不是一个简单的“构造方法加属性设置”能搞定的。创建过程有多步装配先初始化资源再建立连接再注册监听器。写进配置里会非常臃肿写进一个工厂类更好维护。创建过程需要动态代理比如要生成工具类、代理对象的代理逻辑直接在配置里根本写不出来必须用代码表达。创建过程需要屏蔽细节使用方只关心拿到什么类型的产品不关心你怎么造出来的。所以 FactoryBean 是 Spring 给“复杂 Bean 创建过程”预留的官方扩展点。很多底层整合框架都用它来解决老问题例如某个持久层框架把自己的 Mapper 接口注册为动态代理 Bean本质上就是通过一个 FactoryBean 实现容器登记的类是一个 FactoryBean 类型最终返回给使用方的却是 Mapper 的动态代理实例。还有 Spring 自己的 AOP 代理机制里早期也是通过工厂 Bean 形态把代理逻辑包进去的。理解了这一点你再看很多框架源码里的 FactoryBean 实现思路会顺很多。3.4 关于 FactoryBean 的三个经典坑第一getBean返回类型和预期不符。当你要的是产品类型却以为能拿到 FactoryBean或者反过来类型转换就炸了。排查时先看看是不是漏写了前缀。第二isSingleton()返回 false 时每次getBean都会重新执行getObject()如果你的getObject()创建成本很高性能堪忧。第三getObject()里抛出的异常会包裹在BeanCreationException里具体原因要看cause不要只看外表。4. ObjectFactory延迟获取对象也是循环依赖处理里的关键角色4.1 一个方法的接口解决一个时机问题ObjectFactory 接口比 FactoryBean 还小public interface ObjectFactoryT { T getObject() throws BeansException; }它只有一个方法。注入的时候Spring 不会立刻创建你需要的那个 Bean而是先给你一个“获取器”等到你真正调用getObject()那一刻容器才会去查找并返回对应的对象。这个设计解决的核心问题是“时机”有些对象的创建时机不该由构造阶段决定而该由第一次使用时决定。举个例子你在一个服务的字段里写Autowired ObjectFactoryOtherService otherService那么这个服务初始化时根本不会触发 OtherService 的创建。等到业务代码真正调用otherService.getObject()时才去容器里拿 OtherService 实例。这种延迟获取在依赖初始化顺序不稳定的场景里非常有用可以帮你绕开启动期的“鸡生蛋”问题。4.2 循环依赖中的三级缓存为什么必须“过一道手”很多讲循环依赖的文章都会提到三级缓存但少有人讲清楚为什么第三级要放 ObjectFactory而不是直接放一个 Bean 引用。先回忆一下三个缓存的结构一级缓存singletonObjects存放完全初始化好的成品 Bean。二级缓存earlySingletonObjects存放早期暴露出的对象这些对象可能还没有完成完整初始化。三级缓存singletonFactories存放beanName - ObjectFactory也就是一条“怎么拿早期对象”的获取路径。假设 A 和 B 互相依赖。创建 A 时A 的实例化完成了但完整初始化还没结束此时如果 B 来依赖 A直接拿 A 的原始实例显然不够因为 A 后面可能还会被 AOP 代理。Spring 的做法是先把一个ObjectFactory放进三级缓存工厂内部会走到getEarlyBeanReference这样的早期引用判断。当 B 创建过程中需要 A 时容器在一级、二级缓存都找不到成品就在三级缓存里拿到 ObjectFactory调用getObject()得到 A 的当前引用再把它放到二级缓存最后把这个引用交给 B。这样 A 就能以“未完全初始化但可引用”的状态提前暴露给 B。核心问题来了为什么不直接放一个普通引用因为 Spring 在最后才能确定这个 Bean 到底需不需要生成 AOP 代理。如果直接把原始实例放进二级缓存一旦之后发现需要代理已经暴露出去的引用还是裸对象切面就失效了。而放一个 ObjectFactory等有人来取的时候再判断“这次取用我要不要生成代理引用”就可以把决策延后到真正需要的时刻。这就是三级缓存非要放 ObjectFactory 而不放普通引用的原因。4.3 除了循环依赖ObjectFactory 日常怎么用日常开发里ObjectFactory 最常见的使用场景是“避免初始化顺序问题”。我接过一个线上问题启动时某个服务依赖另一个外部配置中心连接器连接器初始化太慢导致服务启动失败。后来把连接器改成ObjectFactoryConfigCenterConnector注入真正处理请求时才首次getObject()启动流程瞬间稳定了。本质上就是把“初始化依赖”挪到“首次使用”让系统具备了懒加载的弹性。另外如果候选 Bean 可能不存在或者可能不止一个更推荐用ObjectProviderT。它是 ObjectFactory 的扩展接口在getObject()基础上补充了getIfAvailable()、getIfUnique()、stream()这类更安全的方法Component public class NotificationService { private final ObjectProviderMessageSender senderProvider; public NotificationService(ObjectProviderMessageSender senderProvider) { this.senderProvider senderProvider; } public void send(String msg) { MessageSender sender senderProvider.getIfAvailable(); if (sender ! null) { sender.send(msg); } } }这样即使容器里没有 MessageSender 的实现启动也不会报错调用的时候做判空即可。在日常工程里ObjectProvider比裸 ObjectFactory 用得更顺手但理解 ObjectFactory 依然很重要因为ObjectProvider就是建立在它之上的。5. 实战识别指南当三个名字同时出现在代码里5.1 看签名对号入座面对一段陌生代码怎么快速判断它用的是哪个概念我的经验是直接看签名看到的代码说明什么我该怎么理解context.getBean(orderService)调用 BeanFactory 体系的方法你是在向容器要一个 Beancontext.getBean(productFactory)要的是 FactoryBean 本身你要领工厂回去而不是要产品某个类implements FactoryBeanT这个 Bean 是个特殊生产者注入 T 类型拿到的是产品注入 FactoryBean 类型拿的是工厂Autowired ObjectFactoryT xxx延迟获取 TT 的创建被推迟到调 getObject() 时Autowired ObjectProviderT xxxObjectFactory 的增强版可以用 getIfAvailable 做安全获取这套识别规则看起来简单在实际排查代码时非常管用。你只需要先确定代码里出现的是“容器接口”“Bean 定义接口”还是“延迟获取接口”三者立刻就不会混了。5.2 几种典型的翻车现场与定位手段我整理几个真实翻车场景你们大概率也遇到过。翻车现场一getBean类型对不上。你想拿产品结果 getBean 返回的是工厂实例或者你想拿工厂结果拿到了产品。定位方法很简单在测试里分别用两种方式取一次再对比类型Object withPrefix context.getBean(complexConnection); Object withoutPrefix context.getBean(complexConnection); System.out.println(withPrefix.getClass()); System.out.println(withoutPrefix.getClass());打印结果会非常直观地告诉你谁是谁。翻车现场二把 FactoryBean 定义当普通 Bean 注入。比如你写Autowired ComplexConnectionFactoryBean factory;却打算用它发起连接实际拿到的是工厂对象你需要调factory.getObject()才能得到连接。这种问题一般出现在对 FactoryBean 机制不熟时好在这类代码在编译期大多能看出端倪。翻车现场三循环依赖报错。凡是依赖注入引发的BeanCurrentlyInCreationException第一步看是不是构造器注入——构造器注入的循环依赖基本无法通过三级缓存解决因为构造器调用时必须直接拿到依赖工厂还没来得及暴露早期引用第二步看是否有 AOP 代理叠加代理决策会让循环依赖链路更长排查时用忽略代理的方式验证也是常用手段。翻车现场四ObjectFactory的getObject()调用时才报错。因为它把获取动作延迟了所以 Bean 不存在的错误不会在启动阶段暴露而是延迟到业务调用时。这就是为什么我推荐默认用ObjectProvider的getIfAvailable。如果确实要用 ObjectFactory记得做好异常兜底。5.3 三者组合起来是什么画面你可能会问这三个东西真会同时出现在一个系统里吗会而且不少。我见过一个老系统的连接管理模块是这么设计的系统里定义一个ConnectionFactoryBean来统一生产配置中心连接容器管理这个工厂业务侧持有ObjectFactoryConnection在真正需要下发配置时才调getObject()当配置发生热更新时扩展代码通过底层 BeanFactory 重新注册工厂定义让下一次ObjectFactory.getObject()自然流向新工厂。一个模块里BeanFactory 负责重新注册和管理定义FactoryBean 负责封装连接生产过程ObjectFactory 负责延迟取用三者各管一段恰好组成一条完整链路。理解了这个组合你再看它们的协作关系就不会觉得它们是孤立概念了。6. 顺着 getBean 走一遍三个角色在一趟调用链里如何依次登场6.1 一次 getBean 的抽象旅程我们可以把一次context.getBean(orderService)的调用过程抽象成下面几步ApplicationContext.getBean把请求委托给内部的 BeanFactory 实现。容器解析别名、判断 scope进入单例 Bean 的获取逻辑。先在缓存里找成品找不到且允许早期引用时会查二级、三级缓存。这里ObjectFactory可能登场从三级缓存取到工厂调用getObject()得到早期引用放入二级缓存。缓存中没有就执行创建流程实例化、属性填充、初始化。创建完成后Spring 会检查这个 Bean 是不是FactoryBean。如果是并且调用方要的是产品而不是工厂本身就会走getObjectForBeanInstance调用getObject()拿出产品再继续后面的逻辑。最终返回给调用方。也就是说BeanFactory 始终是这条链路的执行者和调度者FactoryBean 在“创建完成、准备返回”这个节点上负责把产品替换掉ObjectFactory 则可能在“检查缓存、提前暴露”这个节点上被触发。6.2 三个角色在链路里的具体职责角色出场位置职责BeanFactory调用链起点、缓存检查、创建执行分发请求、管理生命周期、维护三个缓存FactoryBean实例创建完毕后的取用阶段用getObject()生成产品决定产品单例还是多例ObjectFactory缓存未命中、允许早期引用时延迟暴露早期对象让后续代理决策可以推迟这张表把三者放进了同一条时间线很多人在这一步会豁然开朗原来它们不是平级概念而是在容器处理 Bean 的不同阶段各司其职。6.3 想深入的话打断点比背源码高效我不建议一上来就去背DefaultSingletonBeanRegistry的每一行。更好的做法是在自己的工程里做一次实验。第一步注册一个简单 FactoryBean。第二步在getBean(xxx)和getBean(xxx)两种调用上打断点。第三步在DefaultSingletonBeanRegistry的getSingleton(String beanName, boolean allowEarlyReference)方法上打断点观察三级缓存到二级缓存的转移。第四步在AbstractBeanFactory的getObjectForBeanInstance方法上打断点看 FactoryBean 产品是怎么被提取出来的。走一遍打断点之后再回去看源码文档你会发现以前晦涩的代码突然都有了具体的画面感。过程比背结论重要得多。我自己带过不少新人也做过很多内部分享。说到最后我总会建议大家不要试图一口吞下三个概念的全部源码先把它们安放到三个角色里BeanFactory 是容器FactoryBean 是容器里那个负责出复杂产品的特殊工人ObjectFactory 是容器发给调用方的一张延迟取货单。只要记住这三个角色再遇到前缀、遇到ObjectProvider、遇到循环依赖的诡异报错你至少能迅速判断出应该往哪条线查。有一次我在一个系统里看到getBean(configFactory)这种写法旁边同事还在困惑为什么拿到的不是配置对象我说这是要工厂本身他试了一下果然如此。那块滚了无数次的石头在那一刻终于停住了。希望你下一次遇到这三个名字时也能和我一样知道自己拿的到底是容器、是生产者还是那张取货单。