1. 名字就差一个单词,却让无数面试者当场翻车
先说说我见过的一个高频翻车场景。很多候选人,简历上写着"熟练掌握Spring核心原理",开场聊得不错,等到面试官抛出这个题目:"BeanFactory和FactoryBean有什么区别?"——第一反应往往是:"FactoryBean是BeanFactory的实现类吧?"这个回答一出,基本可以确定前面聊的含金量要打折了。
为什么这个问题杀伤力这么大?因为它精准地切在了一个认知盲区上:你把这两个概念当成了同一个维度上的两个东西来比较,但实际上它们根本不在一个位面。
BeanFactory是一个工厂,管的是"所有Bean的创建和生命周期";FactoryBean是一个Bean,它自己是一个特殊类型的对象,被Spring管着,同时它还能"生产"别的对象。一个是容器,一个是容器里的一个"工具人"——这俩怎么能是父子关系呢?就好比你问"食堂和食堂里的一个厨师有什么区别",正确答案不是"厨师是食堂的子类",而是"食堂是提供饭菜的地方,厨师是其中负责做饭的人,食堂除了厨师还有打饭窗口、采购员、收银员,厨师做出来的菜也不一定都出自同一个窗口"。
这篇文章我打算换个角度来写,不是简单背一套面试答案,而是把这个题目拆开揉碎,讲清楚BeanFactory的职责边界、FactoryBean到底解决什么实际问题、两者的协作链路,以及面试官抛出这个问题之后通常会连环追问哪几个点。把这篇文章看完,你不光能答上一个题,还能把Spring容器创建对象这件事的底层逻辑顺一遍。
2. BeanFactory:不只是"一个工厂",是整个IoC集装箱的总闸门
2.1 BeanFactory在Spring里的坐标
要理解BeanFactory,先理解它诞生的场景。Spring的核心卖点是IoC(控制反转)和DI(依赖注入),说人话就是:你不需要自己new对象,不需要自己管理对象的依赖关系,把这件事交给容器去做。而容器"管"对象的入口,就是这个BeanFactory接口。
BeanFactory是Spring IoC容器的根接口,它定义了一个对象容器最基本的操作能力:根据名字获取Bean、判断是否包含某个Bean、查询Bean的类型、判断Bean是不是单例。说白了,它给外部提供了一套统一的查对象、取对象的窗口。你在代码里写applicationContext.getBean("userService"),最终走到的就是BeanFactory体系里的实现逻辑。
这里有个容易混淆的点要提前说清楚:ApplicationContext和BeanFactory是什么关系?很多人被问懵是因为Spring官方文档里经常出现这两者混着说。简单讲,ApplicationContext是BeanFactory的一个增强型子接口,它在BeanFactory的基础上增加了国际化支持、事件发布、资源加载、环境抽象等企业级功能。实际开发中你几乎不会直接new一个XmlBeanFactory这种原始实现(Spring 6里已经标记为过时),而是直接用ApplicationContext体系,但ApplicationContext内部的"内核"仍然是BeanFactory。
面试时如果被问到"为什么不用BeanFactory而用ApplicationContext",可以从两个维度回答:一是功能完整性,ApplicationContext默认就有事件机制、AOP自动代理、国际化这些能力;二是单实例预加载的差异,BeanFactory在第一次getBean时才创建对象(懒加载),ApplicationContext在启动阶段就把单例Bean创建好了(预实例化)。预实例化的好处是能在启动时尽早暴露配置错误,而不是等到第一笔业务请求来了才炸。
2.2 BeanFactory到底怎么管理Bean的生命周期
BeanFactory的核心工作可以拆成这么几步:解析Bean定义 → 创建实例 → 填充属性 → 执行初始化回调 → 放入缓存 → 对外提供服务 → 销毁时执行销毁回调。
当Spring启动时,它会读取配置(XML、注解、Java Config),把每一个<bean>或@Bean、@Component描述的信息转换成BeanDefinition对象。这个BeanDefinition不是Bean本身,它只是一份"图纸",记录了Bean的类名、作用域、初始化方法、属性值、依赖关系等元数据。图纸有了,工厂才开始照着"建房子"。
建房子的过程有几个关键钩子,面试中被问到"Spring Bean的生命周期"时,背的就是这一串方法调用的顺序。我按照Spring 5到Spring 6的实际行为顺序列一下简化版:
- 实例化(通过构造器或工厂方法创建原始对象)
- 属性填充(依赖注入,这里会处理
@Autowired、@Resource) - 调用
BeanNameAware、BeanClassLoaderAware、BeanFactoryAware的回调 - 调用
BeanPostProcessor.postProcessBeforeInitialization - 调用
InitializingBean.afterPropertiesSet(如果XML配了init-method也会执行) - 调用
BeanPostProcessor.postProcessAfterInitialization(AOP代理就是在这个环节生成的) - 放入单例缓存池,开始被使用
- 容器关闭时,调用
DisposableBean.destroy和配置的destroy-method
你注意看第1到第7步,这些操作执行在什么范围之内?答案是BeanFactory的getBean方法调用链里。换句话说,BeanFactory是整个生命周期流程的总调度者。你在Spring Boot里写一个@Configuration配置类,声明了一个@Bean方法,这个方法的返回值本质上也是通过这条链路被注册和管理的。
2.3 getBean的背后逻辑
getBean(String name)是整个BeanFactory最核心的方法,没有之一。它的内部逻辑大致是这样:
- 先从单例缓存池里查(一级缓存singletonObjects)
- 如果没有,再查是否正在创建中(二级缓存earlySingletonObjects,解决循环依赖时用)
- 还不够,再去查三级缓存singletonFactories,可以提前暴露对象工厂来打破循环依赖
- 缓存里始终没有,就按照BeanDefinition的描述,走一遍前面说的创建流程
这里我要插一个面试锦囊:如果面试官让你画出三级缓存解决循环依赖的图,你最好能反映一个细节——不是所有Bean都能用三级缓存解决循环依赖,构造器注入的循环依赖无法通过提前暴露半成品对象来解决,因为半成品都还没创建出来就已经卡死了。另外,prototype作用域的Bean本身就无缓存可言,循环依赖直接抛异常。这两句话如果能在面试中脱口而出,档次立刻不一样。
3. FactoryBean:一个能"造Bean的Bean",解决的是复杂对象创建的痛点
3.1 为什么有了BeanFactory还需要FactoryBean
这是整个题目的灵魂问题。BeanFactory不是已经能创建对象了吗?为什么还需要一个叫FactoryBean的东西来"辅助创建"?
答案是:BeanFactory只能按照BeanDefinition的描述,通过构造器或静态工厂方法"中规中矩"地创建对象,但实际业务里存在大量无法用这种常规路数创建对象的场景。
举几个例子:
- 对象本身是某个框架动态生成的,比如通过动态代理生成的对象,你写不了它的构造器
- 对象需要根据复杂的配置参数,在运行时组装,比如根据字符串类型的规则文本,生成一个规则引擎
- 对象的创建过程需要访问容器内部的资源,比如把某个MBean注册到JMX容器
- 对象本身是个很轻量的封装,但内部需要拿到Spring容器里的其他Bean,再做一层转换
这些场景下,你自己写一个类,实现org.springframework.beans.factory.FactoryBean<T>接口,重写三个方法:
public interface FactoryBean<T> { T getObject() throws Exception; // 返回真实要用的对象 Class<?> getObjectType(); // 返回对象的类型 boolean isSingleton(); // 是否单例 }然后把这个FactoryBean本身注册成一个Bean。Spring在处理这个BeanDefinition时,发现这个Bean实现了FactoryBean接口,就会在初始化后调用它的getObject()方法,把返回结果作为真正的Bean暴露给外部使用。
你可以把FactoryBean理解成一个"自定义制造车间":BeanFactory是总装厂,它只管按流程走;但有些零件没法走标准生产线,那就设一个专门的自定义工位——这个工位就是FactoryBean,它接收原材料(依赖的Bean),然后通过getObject()输出精加工后的产品。
3.2 FactoryBean在全生态里的经典用例
Spring框架自身就大量用FactoryBean来武装自己。很多你在Spring Boot里常用到的功能,底层本质就是某个FactoryBean在干活。举几个最典型的:
当你用XML或注解声明了一个
@Bean返回DataSource,Spring的DataSource创建过程通过DriverManagerDataSource等实现来搞定,这不算FactoryBean。但如果你使用JndiObjectFactoryBean,就进入了FactoryBean的范畴,它能从JNDI资源环境里捞出一个DataSource对象出来给你。当你声明一个
@Mapper注解的接口时,底层通过MapperFactoryBean(某持久层框架的集成模块)为每个接口生成动态代理对象,注入到容器中。所以你从来没写实现类,却可以直接@Autowired一个Mapper接口,用的就是FactoryBean机制。当你需要给某个Bean织入事务能力、AOP切面时,
ProxyFactoryBean会为你的目标对象生成一个增强代理,然后在Spring容器里注册这个代理对象作为真正的Bean。在Spring的缓存抽象里,
ProxyCachingFactoryBean、JCacheFactoryBean这类实现也都存在。
这些例子的共性是:它们生产的都不是普通类实例,而是代理对象、增强对象、远程连接封装对象,这种"不是常规构造器能new出来的东西",就只能交给FactoryBean去解决。
4. 核心区别对照:一个管容器,一个管产品,面试答案在这里拉开差距
4.1 一张表把底牌说清
面试官问出"有什么区别"之后,真正想听的不是你背概念,而是你能不能同时从多个维度去对照。下面这张表,建议原封不动记在脑子里:
| 对比维度 | BeanFactory | FactoryBean |
|---|---|---|
| 本质 | IoC容器的根接口,是一个"容器"的概念 | 一个用于产生其他对象的接口,是一个"特殊Bean" |
| 隶属关系 | 管理容器内所有Bean的生命周期 | 本身也是容器内的一个Bean,同时能创造别的Bean |
| 是否直接向开发者使用 | 通常不直接使用,通过ApplicationContext间接使用 | 通常在创建复杂对象时实现该接口 |
| getBean("xxx")返回什么 | 返回上下文中的Bean实例 | 返回getObject()的产物 |
| getBean("&xxx")返回什么 | - | 返回FactoryBean自身 |
| 创建对象的方式 | 根据BeanDefinition走构造器/静态工厂方法流程 | 由自定义getObject()逻辑完全控制 |
| 典型实现 | XmlBeanFactory、DefaultListableBeanFactory | Mybatis的MapperFactoryBean、Spring的ProxyFactoryBean |
| 单例管理 | 单例Bean默认由容器缓存并管理 | isSingleton()方法定义产品是否为单例,工厂本身的单例由容器决定 |
这张表的信息量很大,展开说三点:
第一,getBean("xxx")和getBean("&xxx")是这个题里最容易考动手能力的点。如果面试官抛出"当Spring容器里注册了一个FactoryBean,我们调用getBean时拿到的是什么"——大多数人都知道拿到的是getObject()的返回值,但能做到脱口而出"前缀&可以拿到工厂本身"的人就少了。这一句话就把你从"背过面试题"和"真懂机制"划分开了。
第二,FactoryBean的产品单例由谁管?要注意,isSingleton()返回true时,产品对象会被FactoryBean内部缓存(存到FactoryBeanRegistrySupport里的factoryBeanObjectCache);返回false时,每次getObject()都会新建。但工厂自身(那个FactoryBean实例)是不是单例,取决于你在BeanDefinition里配置的scope。这两个"单例"不是一个层面的事,很容易被混着谈。
第三,BeanFactory是接口,Spring Boot里它最常见的实现类是DefaultListableBeanFactory。这个类承担了几乎所有注册Bean、获取Bean、处理依赖、合并BeanDefinition的底层活。你如果能把"DefaultListableBeanFactory是默认实现"挂在嘴边,说明你读过Spring容器源码的关键位置。
4.2 创建过程的分工差异,一个可能被忽略的考点
这里还有一道很刁的面试衍生题:"请问FactoryBean本身的生命周期是由谁管理的?"答案当然还是BeanFactory。FactoryBean作为一个实现了工厂接口的特殊Bean,在被注册为Bean后,它自身照样会执行属性填充、Aware回调、BeanPostProcessor的前置和后置处理。Spring容器压根不会因为它"能造其他Bean"就给它开特权。也就是说:
- 它是Bean,被BeanFactory管理,所以它也有生命周期,也有依赖注入
- 它是工厂,被外部使用,所以它负责给外部提供产品
- 它在容器初始化时自己先被创建,然后容器再调用它的
getObject()把产品注入到其他需要的地方
你可以理解为:FactoryBean是一个身上背着"生产线"的普通员工,他本人得先入职(被实例化)、领到工牌(被注入依赖),然后他的生产线才能开工(getObject)。
5. 面试连环追问:光会说区别还不够,这几个坑才是真正的分水岭
5.1 "在getBean("&userFactory")这个操作里,&的作用是什么"
前面其实已经提到了,但单独拿出来说,是因为很多面试官会在这个问题上给候选人设一个"障眼法"。有人以为&是从字符串取地址,或者是什么转义符号,其实它是Spring容器内部定义的一个前缀规约,在AbstractBeanFactory.getBean的解析逻辑里,BeanFactory.FACTORY_BEAN_PREFIX = "&",当name以&开头时,返回的是FactoryBean本身而不是它的产品。
面试时回答这个问题的满分模板:
"Spring的BeanFactory通过getBean(String name)查询Bean时,如果这个Bean实现了FactoryBean接口,默认返回的是它getObject()的产品对象;如果需要拿到工厂实例本身,就在Bean名称前加一个&前缀。实际开发中,MyBatis的Mapper接口通过MapperFactoryBean生成动态代理,当我们想排查某个FactoryBean的真实创建的代理对象细节时,可以先getBean("&xxx")拿到工厂,再手动调用getObject()来调试。"
这番话一出口,既讲清楚了机制,又说出了实际的调试场景,比干巴巴背定义强太多了。
5.2 如果FactoryBean生成的对象需要被用到别处,容器的依赖注入怎么处理
这是另一个容易卡壳的连问。场景是:有一个Service需要注入一个由FactoryBean产出的对象,我们能不能直接@Autowired这个产品类型?
这里要分两家说。如果只是简单注入,Spring容器在依赖注入时同样会走getBean的机制,那么产品本身是能通过类型或名称匹配被注入的。但如果你在同一个类型上既有FactoryBean产出的对象,又有普通类型的Bean实例,类型匹配就会出现歧义,需要@Qualifier来指定名称。
更棘手的问题是:如果我想注入FactoryBean本身,而不是产品,怎么办?直接@Autowired一个FactoryBean类型的字段,Spring依然会尝试去找工厂本身(因为类型匹配的是FactoryBean实现类),这时候你就成功注入了工厂。但如果那个FactoryBean实现类同时生产的产品类型和工厂类型存在继承关系,又有可能引发歧义,这个细节比较深了,面试中能提到"注入FactoryBean本身要格外注意类型歧义"就足以证明你实操过。
5.3 "BeanFactory和FactoryBean的执行顺序是怎么样的"
这也是面试官爱问的"开脑洞"问题。答案是:
- 容器启动解析配置,生成BeanDefinition
- 当实例化到某个BeanDefinition时,发现对应Class实现了FactoryBean接口
- 先通过构造器实例化FactoryBean本身(走一遍普通Bean的生命周期)
- 容器在完成FactoryBean自身属性填充和初始化后,调用
getObject(),拿到产品对象 - 如果
isSingleton()为true,产品对象被工厂内部缓存 - 后续外部getBean("名字")直接返回缓存产品或重新调getObject()
换句话说,FactoryBean自身必须先于它的产品创建,这个顺序是硬性的。这就像你不能先吃到厨师还没入职的餐厅的菜。
6. 动手验证:写一个自定义FactoryBean,把机制踩在脚下
6.1 手写一个"可配置消息生成器"
光说不练假把式。我这里用一个极简的Demo,带大家完整地走一遍FactoryBean的创建和取用流程。场景是这样的:应用里有多个消息推送场景,需要根据不同的配置生成不同的通知器对象,而通知器对象的创建要经过复杂的参数校验、组合、包装过程,不适合写成普通的@Bean方法。
第一步,定义一个产品接口:
public interface MessageNotifier { void send(String title, String content); }第二步,写两个简单的产品实现类,负责不同渠道的消息发送逻辑:
public class EmailNotifier implements MessageNotifier { private String serverHost; private String fromAddress; public EmailNotifier(String serverHost, String fromAddress) { this.serverHost = serverHost; this.fromAddress = fromAddress; } @Override public void send(String title, String content) { System.out.println("[Email] from " + fromAddress + " via " + serverHost + " -> " + title + ": " + content); } } public class SmsNotifier implements MessageNotifier { private String gatewayUrl; private String signName; public SmsNotifier(String gatewayUrl, String signName) { this.gatewayUrl = gatewayUrl; this.signName = signName; } @Override public void send(String title, String content) { System.out.println("[Sms] " + signName + " to gateway " + gatewayUrl + " -> " + title + ": " + content); } }第三步,写一个FactoryBean,根据配置文件中传入的渠道类型,动态生成不同的通知器:
public class MessageNotifierFactoryBean implements FactoryBean<MessageNotifier> { // 这个属性会被Spring容器注入 private String channelType; private String host; private String fromAddress; private String gatewayUrl; private String signName; @Override public MessageNotifier getObject() throws Exception { // 这里可以做很复杂的组装逻辑 if ("email".equalsIgnoreCase(channelType)) { // 创建前可以做参数校验、网络连接等初始化操作 return new EmailNotifier(host, fromAddress); } else if ("sms".equalsIgnoreCase(channelType)) { return new SmsNotifier(gatewayUrl, signName); } throw new IllegalArgumentException("unsupported channelType: " + channelType); } @Override public Class<?> getObjectType() { return MessageNotifier.class; } @Override public boolean isSingleton() { // 让产品也走缓存,避免每次拿通知器都重新创建 return true; } // getter / setter 略 }第四步,在配置类里注册它:
@Configuration public class NotifierConfig { @Bean("messageNotifierFactory") public MessageNotifierFactoryBean messageNotifierFactoryBean( @Value("${notifier.channelType}") String channelType, @Value("${notifier.email.host}") String host, @Value("${notifier.email.from}") String fromAddress, @Value("${notifier.sms.gatewayUrl}") String gatewayUrl, @Value("${notifier.sms.signName}") String signName) { MessageNotifierFactoryBean factory = new MessageNotifierFactoryBean(); factory.setChannelType(channelType); factory.setHost(host); factory.setFromAddress(fromAddress); factory.setGatewayUrl(gatewayUrl); factory.setSignName(signName); return factory; } }注意,关键就在@Bean方法返回的是FactoryBean实例本身,但Spring容器里最终可用的Bean是它的产品。由于isSingleton返回true,产品只会在第一次getBean("messageNotifierFactory")时通过getObject()构建,并缓存下来。
第五步,在代码里使用:
@Service public class NotificationService { @Autowired private MessageNotifier messageNotifier; public void notify(String title, String content) { messageNotifier.send(title, content); } }这里@Autowired匹配的是MessageNotifier类型,Spring发现容器实际注册的Bean是FactoryBean产出的产品,就直接给注入进去了——整个过程对业务方完全透明。
6.2 实验观察:getBean和getBean("&")的差别
把上述代码跑起来之后,加一个测试方法,验证我们的理解:
public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(NotifierConfig.class); // 第一次getBean,触发工厂创建产品 Object notifier = context.getBean("messageNotifierFactory"); System.out.println("getBean -> " + notifier.getClass().getName()); // 加&前缀,拿回FactoryBean本身 Object factoryBean = context.getBean("&messageNotifierFactory"); System.out.println("getBean(&) -> " + factoryBean.getClass().getName()); // 判断两者是否同一个对象 System.out.println("same object ? " + (notifier == factoryBean)); }运行结果会类似下面这样:
[Email] ... (这是getObject中模拟的连接日志之类,如果写了的话) getBean -> com.example.factorybean.EmailNotifier getBean(&) -> com.example.factorybean.MessageNotifierFactoryBean same object ? false看这个结果,一切就很直观了。我们多次强调的机制,用一行代码就验证清楚了。这个实验要是在面试前亲手跑一遍,比背任何面试题都管用,因为你对"产品"和"工厂"这两个角色的亲身感受,和听别人讲完全是两码事。
6.3 这类设计在实际工程里的定位
你会问,这个Demo看起来有点简单,实际生产环境里真的有人自定义FactoryBean吗?答案是:有,但没有你想象的那么多。大多数模板代码、代理对象生成、集成适配工作,框架都已经提供了现成的FactoryBean实现,比如我之前提到的ProxyFactoryBean、MapperFactoryBean等,普通业务开发者直接用就行。
你自己手写自定义FactoryBean的场景主要是这些:
- 你在写一个基础组件或中间件,想给使用方一种"透明获取复杂对象"的体验
- 一个对象的构建过程涉及外部系统交互、大量校验、多种分支装配,而这个对象的使用频率又很高,适合用FactoryBean把复杂度收口
- 你需要在Spring容器启动时,对某个外接对象做统一包装、代理改造,同时又要保证外部注入时不用感知工厂
换句话说,这是一个"造轮子"的工具。平时业务代码里用不上,但一旦你用上了,能显著提升组件内部设计的优雅程度。
7. 前后端面试里那些关于FactoryBean的高阶问题,能说出个一二三才是真懂
7.1 为什么问完区别,面试官几乎都会接着问ApplicationContext和BeanFactory的区别
因为"BeanFactory和FactoryBean的区别"这个题,天然带着个概念混淆的姊妹题:ApplicationContext又是什么?BeanFactory到底是不是过时了?很多候选人会陷入"ApplicationContext比BeanFactory多了功能所以更好"这种粗暴结论。但如果你去追根溯源,ApplicationContext在启动时之所以会预实例化单例Bean,是因为它内置了一个preInstantiateSingletons()方法,这才是它的标准行为;而BeanFactory接口本身并不强制预实例化,是否预激活完全取决于具体实现策略。这一层能说清楚的人,才算真正吃透Spring容器的骨架。
7.2 "Spring Boot启动器里,AutoConfiguration的注册流程跟BeanFactory有什么关系"
这也是现在面试里的热门追问题。Spring Boot的自动配置,本质上是往DefaultListableBeanFactory里注册了一系列基于条件和配置决定的BeanDefinition。那么FactoryBean在其中参与了吗?参与了。比如有些自动配置类会import某些FactoryBean注册逻辑,或者某个starter内部用FactoryBean把外部SDK对象包装进来。但更常见的场景是你用@Bean方法返回一个普通对象,此时走的是普通Bean创建链路,和FactoryBean关系不大。面试官问这个问题,是想看你能不能把"容器创建对象"的链路和"Spring Boot自动配置"的启动机制串起来讲。
7.3 高阶陷阱:BeanPostProcessor会不会对FactoryBean生产的产品生效
这个坑值得单独记住。BeanPostProcessor不会直接作用在FactoryBean生产的产品上。一个产品对象是getObject()返回的,它已经跳出了正常Bean生命周期流程,容器不会再执行一遍"实例化 → 属性填充 → 初始化 → BPP增强"的标准线路,所以常规的@PostConstruct、InitializingBean、BeanPostProcessor都不会作用在产品上。如果你想对产品做增强,得在getObject()内部自己完成,或者让FactoryBean本身实现BeanFactoryPostProcessor等间接手段来干预。这一点能主动说出来,说明你真的翻过源码。
有一个不严谨但形象的比喻:BeanFactory像一条传送带,每个Bean从起点走到终点,依次经过各个质检工位(BeanPostProcessor等);FactoryBean是传送带旁边的一个手工组装区,它自己作为Bean经过完整的传送带质检,但它手工组装出来的产品直接就被抱到仓库了,不会再走一遍传送带。
7.4 一个反向视角:FactoryBean和Supplier/Provider有什么区别
作为面试收尾,这里再补充一个发散思考:有了FactoryBean,为什么Spring还支持@Bean方法直接返回一个对象?为什么又搞出了ObjectFactory和ObjectProvider?
其实这些是不同抽象层次的东西。@Bean方法是最常见的声明式工厂方法,它本质上是一个工厂方法模式的应用;FactoryBean是Spring把"工厂方法"封装成了一种Bean类型,走的是接口约定;ObjectFactory/Provider则是延迟查找、延迟获取Bean的注入窗口。三条路都能创建/获取对象,但解决的核心问题不同:
- @Bean方法解决的是"怎么注册自定义构建逻辑"
- FactoryBean解决的是"复杂对象生产过程的标准化封装"
- ObjectProvider解决的是"运行时按需获取、可空获取、多个候选的场景"
如果你能把这些机制放在一个坐标系里讲给面试官听,这题的深度就已经完全超出了普通面试者的答题范围。
8. 写在最后的经验笔记:真正理解这个题目之后,我对Spring容器的很多模糊感觉都打通了
说点个人实战的感悟。这些年我用Spring的时间不算短,刚入行的时候,我其实也曾经把FactoryBean理解成"BeanFactory的一种实现",直到有一次在排查一个莫名的循环依赖问题时,被迫打开AbstractBeanFactory和FactoryBeanRegistrySupport的源码,才发现自己之前的认知全是错的。这个纠错过程带给我一个很大的震动:很多"面试题"之所以经典,并不是靠背答案能应付的,而是它背后真的藏着一套模拟系统运转的模型。
后来我再接触Spring的扩展点时,整个思路清晰了很多。比如我要给某个接口的所有实现加日志增强,就可以不直接修改每个实现类的代码,而是考虑在某处通过一个FactoryBean生产增强代理;我要为某个外部SDK做统一接入,给它包装适配层,也可以考虑通过FactoryBean把适配层对象注册到容器里,让业务方用的时候完全感知不到适配逻辑的存在。
最后给大家一个具体建议:如果你正在准备面试,建议亲手做三件事。第一,写一个自定义FactoryBean并验证getBean名称和&前缀的差异;第二,翻一下FactoryBeanRegistrySupport里缓存产品对象的源码,找到那个getObjectFromFactoryBean方法;第三,画一张BeanFactory或ApplicationContext创建Bean全流程的时序图(在纸上画即可),把FactoryBean自身的生命周期、产品的创建时机、BeanPostProcessor的作用边界都标进去。这三件事做完,你在这个知识点上的深度,大概率就能超过大多数候选人了。
说句实在话,面对"BeanFactory和FactoryBean的区别"这个考题,能把定义背出来的可能只有60%的候选人,能把getBean和&前缀说清楚的,大概只剩20%,能完整说出生周期顺序、产品缓存与单例管理、BeanPostProcessor边界的人,寥寥无几。所以,把这个题当作一个入口,去构建你对整个Spring IoC容器运转方式的理解图景,远比只记住一个标准答案更有价值。