news 2026/10/6 9:51:27

Spring FactoryBean深度解析:反直觉原理、源码与实战避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring FactoryBean深度解析:反直觉原理、源码与实战避坑指南

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 FactoryBean<T> { 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定义Scope@Scope("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); }

但这样有几个问题。第一,如果创建逻辑很长、依赖很多其他Bean,@Bean方法会变得臃肿;第二,如果对象需要通过代理、装饰器层层包装,每增加一层就要改一遍方法;第三,这个创建逻辑没法复用,换个地方想用还得复制一遍。

把创建逻辑抽到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 FactoryBean<CryptoService> { @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容器源码的调用顺序看,整个过程是这样的:

  1. 扫描注册:ConfigurationClassPostProcessor处理@Component,把CryptoServiceFactoryBean注册为BeanDefinition。
  2. 实例化工厂:AbstractAutowireCapableBeanFactory.createBean()走一遍完整的Bean生命周期(属性填充、初始化回调等),得到FactoryBean实例。
  3. 标记缓存判断:DefaultSingletonBeanRegistry.getSingleton()回归后,容器发现这个Bean是FactoryBean类型。
  4. 调用getObject():在FactoryBeanRegistrySupport.getObjectFromFactoryBean()里最终调用getObject(),把产物注册进singletonObjects缓存(如果isSingleton()为true)。
  5. 对外暴露产品:所有后续依赖注入、按类型查找,都是从缓存里拿这个产品对象。

源码里最核心的一段在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实例本身
"user"FactoryBeangetObject()产品
"&user"普通Bean抛异常(非FactoryBean禁止&获取)
"&user"FactoryBeanFactoryBean实例

getObjectForBeanInstance这段逻辑很值得读,很多诡异问题的根源都在这里。我遇到过一次报错,说Bean named '&xxx' is not a FactoryBean,就是因为传了&前缀但目标Bean根本没实现FactoryBean,Spring按约定验证时直接抛错。当时排查了半天才发现是配置写错了对象。

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,实现FactoryBean<T>接口。它的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的ProxyFactoryBean:AOP的底层基础

在Spring AOP的远古版本(以及现在不少老的XML配置项目里),ProxyFactoryBean是创建AOP代理对象的主要入口。它实现FactoryBean<Object>,getObject()方法返回一个代理对象。代理的逻辑交给ProxyFactory和Advisor链处理。

虽然现代Spring Boot项目大多数已经用@Aspect+@EnableAspectJAutoProxy替代了手动配置ProxyFactoryBean,但它的思想依然渗透在Spring的AOP基础设施中。比如AbstractAutoProxyCreator这种后处理器做的事情,和getObjectFromFactoryBean里做的事情,本质上是同一件事:把一个经过包装的对象以Bean的身份暴露给容器。

5.3 其他框架与工具中的影子

  • OpenFeign:FeignClientFactoryBean为每个@FeignClient接口创建远程调用的代理对象。这跟MyBatis的原理几乎如出一辙,只是代理内部发送HTTP请求。
  • Shiro:ShiroFilterFactoryBean用于构建安全过滤链对象。
  • Spring Security:老版本里有FilterChainProxy的构建也借助了FactoryBean。
  • RMI、JNDI:JndiObjectFactoryBean用于从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引用(还没完成属性填充)。
  • 三级缓存singletonFactories:ObjectFactory,用于提前暴露代理的工厂。

那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依赖于B,B是一个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()返回null,Spring无法确定产品类型,只能走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还提供了一个SmartFactoryBean<T>接口,它继承了FactoryBean<T>,额外增加了几个带默认实现的方法:

public interface SmartFactoryBean<T> extends FactoryBean<T> { default boolean isPrototype() { return false; } default boolean isEagerInit() { return false; } }

isPrototype()表示产品是否为原型,和isSingleton()语义上有重叠但更精细;isEagerInit()表示工厂是否需要立即初始化,如果返回true,容器启动阶段就会调用getObject(),而不是延迟到首次注入。

在需要"启动时预创建"的场景,比如预热连接池、预加载配置,isEagerInit()就特别有用。如果用的是一般FactoryBean,Spring默认懒加载产品,只有第一次getBean时才触发创建。我曾经因为忘了设置这个,导致线上第一次请求特别慢。后来排查发现就是懒加载导致的,把isEagerInit()打开以后启动阶段就预热完毕,请求延迟立刻降了下来。

7.5 泛型擦除导致getObjectType类型不符

最后一个坑,也是很多人用FactoryBean配合泛型时踩到的:写了一个AbstractFactoryBean<T>作为基类,子类指定了泛型参数,但getObjectType()里如果直接写return T.class,编译都过不了。就算通过反射去拿泛型参数,过程也比较繁琐。

如果产品类型是泛型,建议在子类构造时通过构造参数显式传入Class或者在子类里覆写getObjectType()。常见的做法是:

public class DemoFactoryBean<T> implements FactoryBean<T> { private final Class<T> targetType; public DemoFactoryBean(Class<T> 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.factory=DEBUG logging.level.org.springframework.beans.factory.support=DEBUG

开了之后你会看到类似Creating shared instance of singleton bean、Trying to create object from FactoryBean之类日志输出,配合断点看getObjectFromFactoryBean的调用栈,比看十篇源码解析都直观。

我在实际项目里做规则引擎时,就用FactoryBean封装过整个规则编译器的构建过程:从规则文本读取、语法解析、AST生成、字节码增强,到最终产出可执行的规则对象。前后端工程上的同事看到只需注入一个RuleEngine接口就能用,完全不需要关心内部构建链路。比起在@Bean方法里堆几十行创建代码,这种封装方式让整个系统的可维护性上了一个档次。

Spring这个框架妙就妙在,它把复杂的容器机制藏得很深,但又在恰到好处的位置留了一些扩展口子。FactoryBean就是这样一个口子:看着简单,用好了却能把框架的能力发挥到极致。希望这篇长文能帮你把它彻底吃透。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 9:51:26

Spring多模块工程新建Module全攻略:从IDEA操作到POM配置与Bean扫描

很多人在 Spring 入门进到一定阶段后&#xff0c;都会撞上同一个困惑&#xff1a;打开一个正常的基于 Spring 的工程&#xff0c;内部不是单一文件夹加一个 pom&#xff0c;而是多个 module 并列排开。有些开源项目甚至一个根目录下挂着七八个子模块&#xff0c;第一次看到时你…

作者头像 李华
网站建设 2026/10/6 9:49:38

OpenShell实战:模块化Shell配置,打造高效命令行工作台

在终端里泡得够久的人&#xff0c;迟早会遇到同一个问题&#xff1a;默认的 Shell 环境越来越不够用。命令敲了一遍又一遍&#xff0c;历史记录翻得手酸&#xff0c;补全总是差那么一点意思&#xff0c;换台机器环境就全乱。OpenShell 就是冲着这些痛点去的——它不是一个具体的…

作者头像 李华
网站建设 2026/10/6 9:49:31

中信银行电商管家支付对接实战指南:从PPT反向工程到国密SM2落地

简介&#xff1a;本资源是中信银行面向撮合型电子商务企业推出的「电商管家」产品官方介绍PPT&#xff0c;聚焦解决电商平台在支付牌照获取成本高、第三方支付机构清算能力弱、合规动力不足及“二清”风险突出等核心痛点。该方案提供集“收、管、付”于一体的全流程资金结算服务…

作者头像 李华
网站建设 2026/10/6 9:49:30

AI安全合规PDF生成指南:从代码到可交付安全文档

简介&#xff1a;本资源是一份聚焦人工智能安全风险与防御技术的深度解析文档&#xff0c;面向AI算法工程师、安全研究人员及高校相关专业师生&#xff0c;系统梳理当前AI模型面临的核心威胁与应对思路。内容涵盖对抗样本攻击&#xff08;图像、语音、文本多模态&#xff09;、…

作者头像 李华
网站建设 2026/10/6 9:47:57

MATLAB物理计算实战:从运动学到有限元

先交代一个背景&#xff1a;我去年帮几个力学系的学生调程序&#xff0c;发现大家十有八九不是物理不会&#xff0c;而是"不知道怎么把物理方程变成MATLAB代码"。比如一个简简单单的抛体运动&#xff0c;有人会用两层循环去一步步积分&#xff0c;有人查了半天符号计…

作者头像 李华
网站建设 2026/10/6 9:47:37

SSR场景下CSP Nonce与Hydration一致性:从原理到实战踩坑指南

上周有个群友把线上SSR项目的CSP策略一开&#xff0c;当天晚上就来找我&#xff1a;所有内联脚本全被浏览器拦死了&#xff0c;页面白屏&#xff1b;好不容易用nonce放行内联脚本&#xff0c;控制台又开始疯狂刷Hydration mismatch警告&#xff1b;顺着警告往下查&#xff0c;发…

作者头像 李华