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.postProcessAfterInitializationAOP代理就是在这个环节生成的放入单例缓存池开始被使用容器关闭时调用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.FactoryBeanT接口重写三个方法public interface FactoryBeanT { 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返回DataSourceSpring的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 一张表把底牌说清面试官问出有什么区别之后真正想听的不是你背概念而是你能不能同时从多个维度去对照。下面这张表建议原封不动记在脑子里对比维度BeanFactoryFactoryBean本质IoC容器的根接口是一个容器的概念一个用于产生其他对象的接口是一个特殊Bean隶属关系管理容器内所有Bean的生命周期本身也是容器内的一个Bean同时能创造别的Bean是否直接向开发者使用通常不直接使用通过ApplicationContext间接使用通常在创建复杂对象时实现该接口getBean(xxx)返回什么返回上下文中的Bean实例返回getObject()的产物getBean(xxx)返回什么-返回FactoryBean自身创建对象的方式根据BeanDefinition走构造器/静态工厂方法流程由自定义getObject()逻辑完全控制典型实现XmlBeanFactory、DefaultListableBeanFactoryMybatis的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 FactoryBeanMessageNotifier { // 这个属性会被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容器运转方式的理解图景远比只记住一个标准答案更有价值。