
1. 反直觉的第一课为什么getBean拿到的不是FactoryBean先讲一个让我印象深刻的场景。刚接触Spring源码那阵子我在项目里定义了一个类实现了FactoryBean接口想着这就是一个工厂Bean嘛注册进去之后BeanFactory.getBean()返回的自然就是这个FactoryBean实例本身。结果一跑测试getBean(myFactoryBean)返回的根本不是我的工厂类而是工厂里getObject()方法生产出来的那个对象。当时脑子是懵的反复看了好几遍代码才确认自己没有看错对象类型。这个反直觉的现象就是理解FactoryBean的第一把钥匙FactoryBean的核心身份是生产者不是产品。它像一条流水线容器里边注册的是这条流水线但你从仓库里领取的是流水线生产出来的商品。后来翻Spring官方文档才彻底理清FactoryBean是一种用来创建复杂对象的接口当Bean的实例化过程太复杂、依赖太多、或者需要运行时动态生成时Spring允许你把这个怎么造的逻辑单独封装进一个FactoryBean里。容器在初始化阶段会先实例化FactoryBean本身然后调用它的getObject()方法得到真正的目标对象再把这个目标对象以Bean的名义暴露给使用方。所以外界Autowired或者getBean()拿到的永远是getObject()的产物除非你刻意用前缀去取工厂本身。这篇博文我想把这些年围绕FactoryBean积累的理解全部拆开讲一遍包括它怎么工作、在框架源码里怎么被玩出花、以及我在实际项目中踩过的几个实打实的坑。适合两类人看一类是对Spring容器运作机制感兴趣、想读源码但不知道从哪儿下手的同学另一类是写业务代码时发现Bean方法搞不定复杂对象创建想找更优雅方案的人。先说一个生活化类比方便后续理解。把Spring容器想象成一个餐厅后厨普通Bean方法就像后厨直接准备好的成品菜端上桌就能吃FactoryBean则像一台咖啡机你点单拿铁后厨不会把咖啡机端给你而是按一下按钮出来的是一杯咖啡。咖啡机是FactoryBean咖啡是getObject()的返回值。你永远喝的是咖啡不是拆开机器喝零件。这个设计最典型的应用场景就是各种框架集成。比如MyBatis的MapperFactoryBean——Mapper接口没有实现类但你可以直接Autowired一个Mapper接口进去靠的就是FactoryBean在getObject()里用JDK动态代理生成实现类。Ribbon、Feign、Shiro等一堆框架也都借用了这个机制。可以说不懂FactoryBean你就没真正看懂Spring生态里一半以上的框架整合源码。2. 三个接口方法搞懂FactoryBean的运行逻辑FactoryBean接口本身非常精简源码就三个抽象方法。但就是这三个方法撑起了Spring容器里所有复杂对象创建的扩展点。public interface FactoryBeanT { String OBJECT_TYPE_ATTRIBUTE factoryBeanObjectType; Nullable T getObject() throws Exception; Nullable Class? getObjectType(); default boolean isSingleton() { return true; } }逐个拆开说。2.1 getObject()容器真正要的那个对象从哪来这是整个FactoryBean的灵魂。Spring容器在完成FactoryBean自身的实例化之后紧接着就会调用getObject()方法把这个方法的返回值注册为目标Bean。这就带来一个关键推论FactoryBean本身的寿命和它生产出来的对象的寿命是两条线。哪怕你的getObject()里写的是new User()每次调用都会生成新实例但工厂本身是单例的整个容器生命周期里只会被实例化一次。所以你在getObject()外部肯定不能维护产品的实例状态如果想做单例产品就得自己加缓存或者用isSingleton()配合容器机制处理。这个方法还有一个细节值得注意它声明抛Exception。意味着你在创建对象时抛出的任何受检异常都可以直接往上抛不需要在FactoryBean内部包装转换。这在创建过程中涉及I/O、反射、网络请求时非常方便。2.2 getObjectType()类型信息就是一本提前写好的说明书getObjectType()返回的是目标对象的类型也就是getObject()生产出来的对象的Class。为什么需要这个方法因为Spring容器在做类型匹配、依赖注入的时候不可能为了知道你生产的是什么类型而先把getObject()跑一遍——那可能会有副作用甚至创建失败。所以设计了一个轻量的类型说明方法。容器在进行Autowired按类型查找、Resource按名称查找时的类型匹配以及applicationContext.getBean(SomeType.class)这种按类型获取Bean的操作都会依赖这个方法的返回值。这里有个大坑我后面会专门讲如果随便返回null会导致Spring在按类型注入的时候不得不强行实例化FactoryBean去探测类型一来性能受损二来可能触发意想不到的初始化。所以凡是能确定类型就老老实实返回类型Class。2.3 isSingleton()产品的生命周期由你说了算默认实现返回true代表产品是单例的容器只会从缓存里取一次getObject()的结果整个上下文共享同一个产品对象。如果返回false那么每次getBean()都会重新调用一次getObject()生产一个新的对象出来。看起来像Scope(prototype)的效果但注意它是FactoryBean层面的控制跟Bean定义上的Scope是平行关系。有一个特别容易误解的点我用一个表格说清楚控制维度写法影响对象Bean定义ScopeScope(prototype)整个Bean的生命周期模式FactoryBean.isSingleton返回false仅影响getObject()产物是否缓存FactoryBean自身单例工厂作为Bean由容器管理默认单例一般不会改变我在网上看到不少帖子把isSingleton()和Scope混为一谈。它们处理的是FactoryBean这个工厂和它产品两个不同层面的生命周期理解错了调试起来很痛苦。3. 手写一个最简FactoryBean感受容器背后的调用链光看接口肯定不够我建议每个学Spring的人都亲手写一个最小的demo把调用顺序打出来看看。我当年就是靠这个实验把FactoryBean彻底看透的。3.1 场景设计为什么我需要一个FactoryBean假设有个需求系统里需要根据配置动态生成一个加密器对象。加密算法可能是AES、DES、SM4具体跑哪一个由配置文件里的crypto.algorithm决定。如果用普通的Bean方法当然也能写Bean public CryptoService cryptoService() { String algorithm config.getAlgorithm(); return CryptoServiceFactory.create(algorithm); }但这样有几个问题。第一如果创建逻辑很长、依赖很多其他BeanBean方法会变得臃肿第二如果对象需要通过代理、装饰器层层包装每增加一层就要改一遍方法第三这个创建逻辑没法复用换个地方想用还得复制一遍。把创建逻辑抽到FactoryBean里就能优雅很多。FactoryBean把创建复杂性封装成可复用的组件容器只需要知道有一个工厂能造出CryptoService就行。3.2 完整代码实现先定义一个策略接口和两个实现类为了简洁我只写一个示意版public interface CryptoService { String encrypt(String data); } Component(aesCrypto) public class AesCryptoService implements CryptoService { Override public String encrypt(String data) { return [AES] data; } } Component(sm4Crypto) public class Sm4CryptoService implements CryptoService { Override public String encrypt(String data) { return [SM4] data; } }重点来了定义一个FactoryBean它读取配置决定返回哪个实现Component public class CryptoServiceFactoryBean implements FactoryBeanCryptoService { Value(${crypto.algorithm:aes}) private String algorithm; Autowired private ApplicationContext applicationContext; Override public CryptoService getObject() { // 根据配置动态选择这里故意用延迟查找而不是直接new return applicationContext.getBean(algorithm Crypto, CryptoService.class); } Override public Class? getObjectType() { return CryptoService.class; } Override public boolean isSingleton() { // 加密器本身是无状态的做成单例省资源 return true; } }注意我把容器注入进来了然后通过getBean()去拿具体的策略实现。这样做的好处是策略对象本身也受容器管理可以继续注入别的依赖。如果直接在FactoryBean里new策略对象那策略对象的依赖就得由FactoryBean自己组装得不偿失。3.3 测试与调用链验证写个测试验证RunWith(SpringRunner.class) SpringBootTest public class FactoryBeanTest { Autowired private CryptoService cryptoService; Autowired private ApplicationContext applicationContext; Test public void testGetBeanReturnsProduct() { // 直接注入的是产品对象而不是FactoryBean System.out.println(cryptoService.getClass()); System.out.println(cryptoService.encrypt(hello)); // 通过容器getBean拿到的也是产品 CryptoService byName applicationContext.getBean(cryptoServiceFactoryBean, CryptoService.class); System.out.println(byName cryptoService); // 加前缀才能拿到工厂本身 Object factory applicationContext.getBean(cryptoServiceFactoryBean); System.out.println(factory.getClass()); } }这里有一个命名细节我稍微提一下后面还会专门展开当FactoryBean的Bean名称是cryptoServiceFactoryBean时你getBean(cryptoServiceFactoryBean)拿到的是产品而getBean(cryptoServiceFactoryBean)拿到的才是工厂。如果JavaConfig里MethodName是cryptoServiceFactoryBean同理。从Spring容器源码的调用顺序看整个过程是这样的扫描注册ConfigurationClassPostProcessor处理Component把CryptoServiceFactoryBean注册为BeanDefinition。实例化工厂AbstractAutowireCapableBeanFactory.createBean()走一遍完整的Bean生命周期属性填充、初始化回调等得到FactoryBean实例。标记缓存判断DefaultSingletonBeanRegistry.getSingleton()回归后容器发现这个Bean是FactoryBean类型。调用getObject()在FactoryBeanRegistrySupport.getObjectFromFactoryBean()里最终调用getObject()把产物注册进singletonObjects缓存如果isSingleton()为true。对外暴露产品所有后续依赖注入、按类型查找都是从缓存里拿这个产品对象。源码里最核心的一段在FactoryBeanRegistrySupport我建议有时间的人直接打开这个类瞄一眼。getObjectFromFactoryBean方法里有几个分支单例模式下取完要放入缓存非单例模式下每次现调另外它还会处理afterSingletonsInstantiated的回调并且会把factoryBeanObjectType这一属性写入BeanDefinition的attribute里。后面这个细节对解决类型推断问题很关键。4. 命名规则与BeanFactory的纠缠符号背后的设计哲学很多初学Spring的人会被FactoryBean和BeanFactory这两个名字搞炸——一个是以Factory命名的Bean一个是管理Bean的工厂单词顺序还一模一样。其实搞清楚命名规则恰恰能加深理解。4.1 为什么是符号Spring设计了一个很特殊的约定在用户传入的Bean名称前面加一个前缀用来获取FactoryBean本身而不是它的产品。这个约定出现在BeanFactory接口的getBean(String name)这个方法里Object getBean(String name) throws BeansException;传入xxx就是获取FactoryBean本身传入xxx就是获取产品。这个设计沿用了很多年也被ApplicationContext继承。所以你在任何能用ApplicationContext的地方都可以用带的名字获取工厂实例。比如前面例子里的applicationContext.getBean(cryptoServiceFactoryBean)返回的就是工厂对象。这在需要操作工厂本身属性、或者动态改变工厂配置的时候特别有用。4.2 符号的解析优先级这里有个容易忽视的细节前缀不只是简单地从Map里查一个名字叫xxx的Bean它影响的是整个Bean解析路径。Spring在AbstractBeanFactory.getBean()里进入doGetBean()流程后有一段专门针对FactoryBean的逻辑叫getBeanForTypeCheck或者后续的getObjectForBeanInstance它负责判断当前名字是带还是不带以及当前实例是否为FactoryBean。四种组合的结论如下表传入名称当前实例类型返回结果user普通BeanBean实例本身userFactoryBeangetObject()产品user普通Bean抛异常非FactoryBean禁止获取userFactoryBeanFactoryBean实例getObjectForBeanInstance这段逻辑很值得读很多诡异问题的根源都在这里。我遇到过一次报错说Bean named xxx is not a FactoryBean就是因为传了前缀但目标Bean根本没实现FactoryBeanSpring按约定验证时直接抛错。当时排查了半天才发现是配置写错了对象。4.3 两个名字的混淆与记忆技巧我的记忆方法是反向记忆BeanFactory是容器是厂房的房东FactoryBean是厂房里的一台机器。房东管理整栋楼机器只负责生产特定产品。一个是从上往下的管理视角一个是被管理的组件视角。看Spring源码时BeanFactory接口和FactoryBean接口出现在完全不同的包前者在org.springframework.beans.factory的根上后者也在这个包里但职责完全不同。BeanFactory是容器顶层接口FactoryBean是个普通的SPI扩展接口。它们俩共同存在这个包增加了新手的迷惑感。5. FactoryBean在Spring生态里的真实身影理论讲完来看实战。FactoryBean最妙的应用恰恰不在你自己的业务代码里而在各种框架整合中。读懂这几个例子你对FactoryBean的理解会从会写变成会读框架源码。5.1 MyBatis的MapperFactoryBean没有实现类的接口凭什么能注入这是最经典的例子。你在MyBatis-Spring里写一个Mapper接口不用写实现类直接Autowired就能注入背后的核心就是MapperFactoryBean。org.mybatis.spring.mapper.MapperFactoryBean继承了SqlSessionDaoSupport实现FactoryBeanT接口。它的getObject()方法大致逻辑是这样的Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); }sqlSession.getMapper()返回的就是MyBatis通过JDK动态代理生成的Mapper实现类。所以你把Mapper接口定义好容器启动时创建MapperFactoryBean调用getObject()动态生成一个代理对象然后以Mapper接口类型暴露给业务代码注入。这个例子强有力地说明了FactoryBean的核心价值它能让一个没有具体实现类的接口变成一个可注入的Bean。有了这层抽象框架的作者可以自由决定Bean的真实类型到底是什么甚至可以像MyBatis一样完全绕开类文件用代理对象填充进去。5.2 Spring的ProxyFactoryBeanAOP的底层基础在Spring AOP的远古版本以及现在不少老的XML配置项目里ProxyFactoryBean是创建AOP代理对象的主要入口。它实现FactoryBeanObjectgetObject()方法返回一个代理对象。代理的逻辑交给ProxyFactory和Advisor链处理。虽然现代Spring Boot项目大多数已经用AspectEnableAspectJAutoProxy替代了手动配置ProxyFactoryBean但它的思想依然渗透在Spring的AOP基础设施中。比如AbstractAutoProxyCreator这种后处理器做的事情和getObjectFromFactoryBean里做的事情本质上是同一件事把一个经过包装的对象以Bean的身份暴露给容器。5.3 其他框架与工具中的影子OpenFeignFeignClientFactoryBean为每个FeignClient接口创建远程调用的代理对象。这跟MyBatis的原理几乎如出一辙只是代理内部发送HTTP请求。ShiroShiroFilterFactoryBean用于构建安全过滤链对象。Spring Security老版本里有FilterChainProxy的构建也借助了FactoryBean。RMI、JNDIJndiObjectFactoryBean用于从JNDI目录查找EJB等外部对象。这些框架的共同点是目标对象的构造过程非常复杂涉及网络、代理、外部目录、动态策略。这些逻辑放在普通Bean方法里会让配置类臃肿不堪而FactoryBean提供了一个专门的创建层还可以配合ConfigurationProperties等机制接收配置。6. 生命周期绕不开的关卡FactoryBean如何参与单例缓存和循环依赖FactoryBean虽然在应用层看起来简单但放进Spring容器的生命周期大框架里就复杂起来。这一节我重点讲它和单例缓存、循环依赖之间的纠缠。这部分内容偏源码但很值得看。6.1 FactoryBean本身也是Bean第一层理解FactoryBean实现了相关接口但它自己首先是一个Bean。这意味着它也要走完整个Bean生命周期——实例化、属性填充、BeanNameAware/BeanFactoryAware回调、InitializingBean回调、DisposableBean销毁等。Spring在创建普通Bean时会先看它是不是FactoryBean类型如果是就会在做完普通Bean的所有初始化之后额外调用getObjectFromFactoryBean来生成产品。这个额外的步骤在AbstractAutowireCapableBeanFactory的createBean逻辑中是一个分支处理。看源码时会发现一个有趣的递归FactoryBean可以生产出另一个FactoryBean。getObject()返回的对象如果也实现了FactoryBean接口那这个产品在对外暴露时要不要继续解析成它的产品答案是不会getObjectFromFactoryBean只在一层上处理不会无限递归。产品是一个FactoryBean那它就作为FactoryBean被注入需要手动用获取时才会触发下一层。6.2 和三级缓存的关系产品是何时进入一级缓存的Spring的单例Bean默认有三层缓存相信大家已经听说过一级缓存singletonObjects完整创建好的单例Bean。二级缓存earlySingletonObjects早期暴露的原始Bean引用还没完成属性填充。三级缓存singletonFactoriesObjectFactory用于提前暴露代理的工厂。那FactoryBean的产品跟这几层缓存什么关系关键在于缓存里存的是FactoryBean本身不是产品。在DefaultSingletonBeanRegistry.getSingleton(String beanName, ObjectFactory? singletonFactory)的createBean过程中FactoryBean作为Bean走完生命周期后被放进三级缓存、二级缓存、一级缓存——但这些都是工厂实例。直到某个消费者调用getBean(factoryBeanName)Spring进入getObjectForBeanInstance才触发getObjectFromFactoryBean进而调用getObject()生成产品。所以在Spring的缓存视角里产品的生命周期完全受工厂实例的生命周期支配工厂还在产品就可以随时被生产工厂销毁时Spring会遍历所有单例Bean看哪些是DisposableBean也会检查FactoryBean的产品是否需要执行destroyMethod如果getObject()返回的对象实现了DisposableBean在getObjectFromFactoryBean里会被记录下来用于容器关闭时回调。这个机制带来的一个实战推论如果你的FactoryBean想管理产品的生命周期比如产品里面有线程池需要shutdown不能只依赖Spring对FactoryBean的销毁回调还需要在destroy()方法里手工销毁产品。因为这些产品在容器关闭时不一定能被Spring自动识别为受管Bean。6.3 循环依赖FactoryBean也会踩这个坑普通的循环依赖Spring可以用三级缓存解决但一旦跟FactoryBean搅在一起事情就不一样。直接说结论解决方案循环依赖的早期暴露本质上暴露的是原始对象引用而FactoryBean在早期阶段还没调用getObject()。如果A依赖于BB是一个FactoryBean的产品而B的getObject()内部又依赖A那么循环依赖就可能处理失败或者拿到的是一个奇怪的代理。为什么因为三级缓存提前暴露的是FactoryBean本尊的ObjectFactory通过getEarlyBeanReference它返回的是FactoryBean实例不是产品引用。A拿到的如果是一个工厂实例而A真正想要的是产品类型就不匹配。Spring在后续阶段会尝试修正但复杂度高、容易出异常比如常见的BeanCurrentlyInCreationException。实践中的建议让FactoryBean的getObject()保持纯函数风格尽量不要在内部依赖其他正在创建的Bean。所有依赖都应该注入到FactoryBean本身而不是在产品创建过程中动态获取。违反这条轻则循环依赖报错重则出现隐蔽的竞态条件。7. 那些年我踩过的FactoryBean的坑说实话FactoryBean本身写起来不难难的是它和Spring容器其他机制碰撞时产生的各种灵异事件。我把自己亲历过的坑整理成一节每个都附上了根因和规避方案希望能帮大家少走弯路。7.1 getObject返回null我曾经在一个FactoryBean里因为配置缺失导致getObject()返回了null。Spring默认会抛FactoryBean threw exception...或者getObject() returned null之类的错误。但这里有一个更隐蔽的坑如果你把getObjectType()正确返回了类型但getObject()返回null在Autowired注入时Spring以为找到了匹配的Bean注入一个null进去业务代码调用时才炸。解决方案是在getObject()里显式检查空值并抛异常宁可快速失败也不要返回null。这是所有FactoryBean实现里最重要的健壮性约束。7.2 getObjectType返回null导致启动变慢类型匹配错乱我犯过最蠢的一个错误偷懒没好好写getObjectType()直接返回了null。结果呢项目启动时间翻了一倍而且一些Autowired注入直接失败报NoSuchBeanDefinitionException。根因也是我去读了源码才明白的当Spring需要按类型查找Bean时如果BeanDefinition中已有明确类型就直接匹配但FactoryBean的getObjectType()返回nullSpring无法确定产品类型只能走getBeanForTypeCheck这条路径强制调用getObject()来探测类型。一个个工厂实例化一遍启动自然慢而且getObject()里如果有副作用比如远程调用那启动时就把远程调用执行了一遍后果可想而知。后来我发现Spring在AbstractBeanFactory里提供了一个优化路径在使用BeanDefinition的attribute(factoryBeanObjectType)时可以直接指定产品类型这样Spring就能跳过探测。JavaConfig的Bean返回类型实际上也能帮助推断但最好还是老老实实实现getObjectType()。7.3 isSingleton返回false时引发的状态混乱有一个需求需要每个线程拿到独立的连接对象所以我把isSingleton()返回了false。但我在FactoryBean里维护了一个共享计数器想统计创建次数结果在高并发下计数错乱。原因是明确之后才发现非单例模式只是不缓存产品对象但FactoryBean本尊仍然是单例的所以共享字段本身就有并发问题。正确的做法是FactoryBean内部的共享状态要么加锁要么用ThreadLocal要么干脆避免共享。一般来说返回false的FactoryBean里除了getObject()之外最好是无状态的。产品创建逻辑需要的参数应该通过方法参数传递或者从FactoryBean的注入属性中读取但不能在多个线程间共享可变状态。7.4 SmartFactoryBean一个容易被忽略的进阶接口Spring还提供了一个SmartFactoryBeanT接口它继承了FactoryBeanT额外增加了几个带默认实现的方法public interface SmartFactoryBeanT extends FactoryBeanT { default boolean isPrototype() { return false; } default boolean isEagerInit() { return false; } }isPrototype()表示产品是否为原型和isSingleton()语义上有重叠但更精细isEagerInit()表示工厂是否需要立即初始化如果返回true容器启动阶段就会调用getObject()而不是延迟到首次注入。在需要启动时预创建的场景比如预热连接池、预加载配置isEagerInit()就特别有用。如果用的是一般FactoryBeanSpring默认懒加载产品只有第一次getBean时才触发创建。我曾经因为忘了设置这个导致线上第一次请求特别慢。后来排查发现就是懒加载导致的把isEagerInit()打开以后启动阶段就预热完毕请求延迟立刻降了下来。7.5 泛型擦除导致getObjectType类型不符最后一个坑也是很多人用FactoryBean配合泛型时踩到的写了一个AbstractFactoryBeanT作为基类子类指定了泛型参数但getObjectType()里如果直接写return T.class编译都过不了。就算通过反射去拿泛型参数过程也比较繁琐。如果产品类型是泛型建议在子类构造时通过构造参数显式传入Class或者在子类里覆写getObjectType()。常见的做法是public class DemoFactoryBeanT implements FactoryBeanT { private final ClassT targetType; public DemoFactoryBean(ClassT targetType) { this.targetType targetType; } Override public Class? getObjectType() { return targetType; } }这样容器在做类型匹配时拿到的就是精确类型不会因为泛型擦除而意外匹配到Object也不会让Spring为了探测类型而提前调用getObject()。8. 写在最后的一点个人经验关于FactoryBean我最后想分享一个判断法则什么时候该用FactoryBean什么时候该用Bean方法我的经验是如果创建对象只需要一两个new、几条配置Bean方法最直观放在Configuration里没人看不懂。但如果创建逻辑本身是一套复杂的算法流程、需要实现特定SPI接口才能接入第三方框架、或者你希望这个创建逻辑可以被多个地方复用那就毫不犹豫上FactoryBean。它把如何构建复杂对象和对象本身彻底分离是一种比Bean方法更高阶的抽象层次。另外给一个实用调试技巧想知道容器里到底发生了什么的同学可以在application.properties里把日志级别调上来logging.level.org.springframework.beans.factoryDEBUG logging.level.org.springframework.beans.factory.supportDEBUG开了之后你会看到类似Creating shared instance of singleton bean、Trying to create object from FactoryBean之类日志输出配合断点看getObjectFromFactoryBean的调用栈比看十篇源码解析都直观。我在实际项目里做规则引擎时就用FactoryBean封装过整个规则编译器的构建过程从规则文本读取、语法解析、AST生成、字节码增强到最终产出可执行的规则对象。前后端工程上的同事看到只需注入一个RuleEngine接口就能用完全不需要关心内部构建链路。比起在Bean方法里堆几十行创建代码这种封装方式让整个系统的可维护性上了一个档次。Spring这个框架妙就妙在它把复杂的容器机制藏得很深但又在恰到好处的位置留了一些扩展口子。FactoryBean就是这样一个口子看着简单用好了却能把框架的能力发挥到极致。希望这篇长文能帮你把它彻底吃透。