1. 从一段报错开始:为什么说BeanFactory是Spring的地基
写Spring的老哥老姐们,肯定都见过这套组合拳:[ClassPathXmlApplicationContext] / [AnnotationConfigApplicationContext] / [FileSystemXmlApplicationContext],拿来就new,new完就getBean,一气呵成。但你有没有在夜深人静的时候,被面试官或者某个较真的同事问过一句:你new出来的那个ApplicationContext,它到底是个什么东西?它跟BeanFactory是什么关系?为什么大家天天喊IOC容器,却很少有人直接拿BeanFactory干活?
我第一次被问住的时候,脑子里的反应是:BeanFactory?好像在哪见过,又好像从来没直接用过的样子。去翻源码,发现ApplicationContext接口的定义第一行写着public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver。好家伙,ListableBeanFactory和HierarchicalBeanFactory都继承了BeanFactory。换句话说,你天天用的ApplicationContext,本质上就是一个功能增强版的BeanFactory。
这篇文章,我打算把BeanFactory和FactoryBean这对长得像、作用却完全不一样的接口彻底讲透。BeanFactory是Spring IoC容器的根接口,定义了getBean、containsBean、isSingleton这些最底层的方法,是整个依赖注入机制的源头。而FactoryBean则是一个专门用来“生产Bean的Bean”,它本身会被容器管理,但getBean的时候容器会返回它生产的那个对象,而不是它自己。这俩名字里都带BeanFactory,但一个是容器级别的顶层抽象,一个是对象级别的创建工厂,搞混的人不在少数。
适合谁看?想搞懂Spring启动时Bean到底怎么被创建出来的源码阅读党,准备面试需要把IOC讲清楚的同学,以及虽然会用SpringBoot但始终觉得底层是个黑盒的开发者。我会从源码和实际行为两个维度来拆,保证你看完之后,再看Spring的startup日志,感受完全不一样。
2. 核心接口拆解:BeanFactory到底定义了哪些规则
2.1 BeanFactory的顶层设计:为什么它是最简接口
先看BeanFactory接口本身。打开Spring源码,找到org.springframework.beans.factory.BeanFactory,你会看到它里面定义的方法其实非常克制,大概就那么十几个,而且是能省则省。这跟很多人的直觉相反——越是底层的接口,越不应该塞太多东西,因为所有实现类都要为你这些方法买单。
来看几个核心方法的定位:
public interface BeanFactory { // 按名字获取Bean,没有就抛NoSuchBeanDefinitionException Object getBean(String name) throws BeansException; // 获取带类型约束的Bean,容器会自动做类型转换或校验 <T> T getBean(String name, Class<T> requiredType) throws BeansException; // 获取该Bean的实例化类型(FactoryBean则返回生产的对象类型) Class<?> getType(String name) throws NoSuchBeanDefinitionException; // 是不是单例(对应scope=singleton) boolean isSingleton(String name) throws NoSuchBeanDefinitionException; // 是不是原型(对应scope=prototype) boolean isPrototype(String name) throws NoSuchBeanDefinitionException; // 是否包含某个Bean定义 boolean containsBean(String name); }注意一个细节:getBean(String name)接收的参数叫name而不是beanName,这是因为Spring里的“名字”有可能带&前缀。这个前缀是什么?你先记住一句话——&beanName获取的是FactoryBean本身,beanName获取的是FactoryBean生产出来的对象。这个机制就是定义在BeanFactory接口的注释里的,源代码顶部注释明确写着:如果Bean是FactoryBean,那么获取时加&前缀返回工厂本身。
2.2 从使用频率看设计动机:为什么ApplicationContext比BeanFactory更贴近日常
BeanFactory接口虽然定义了规则,但实际开发里你几乎不会直接用XmlBeanFactory这种老古董(这玩意儿在Spring 3.1之后就被标记废弃了),而是用ApplicationContext的各种实现。原因在于ApplicationContext在BeanFactory的基础上,叠加了面向企业应用的完整能力:
- 国际化支持:MessageSource
- 事件发布机制:ApplicationEventPublisher
- 资源加载:ResourcePatternResolver
- 环境配置:EnvironmentCapable
- 自动注册BeanFactoryPostProcessor / BeanPostProcessor
我打个比方:BeanFactory是内燃机的缸体,定义了基本的活塞运动规则;ApplicationContext是整台汽车,在缸体外挂上了变速箱、ECU、空调、安全气囊。你开车时不会只抱着缸体跑,但缸体的规则决定了整台车能不能跑。
这里有个非常有意思的点——ApplicationContext内部到底是怎么拿到BeanFactory的?以AnnotationConfigApplicationContext为例,它继承GenericApplicationContext,而GenericApplicationContext里有个字段:
private final DefaultListableBeanFactory beanFactory;看到没有?所有ApplicationContext的实现,内部都会组合一个DefaultListableBeanFactory。DefaultListableBeanFactory才是那个真正干活的类,它实现了BeanFactory、ListableBeanFactory、ConfigurableListableBeanFactory等一堆接口,同时还维护了beanDefinitionMap(Bean定义注册表)、singletonObjects(单例池)这些核心数据结构。
2.3 容器启动的极简链路:从配置类到Bean真的被创建
很多人看Spring源码容易迷路,就是因为在AbstractApplicationContext的refresh()方法里陷进去了。我建议你换个姿势——只盯着一个Bean怎么从配置变成一个实例,这样链路短,也更容易理解BeanFactory的角色。
简化之后的链路是这样的:
读取配置(@Configuration / XML) -> 解析成BeanDefinition -> 注册到DefaultListableBeanFactory的beanDefinitionMap -> 实例化阶段(通过构造器或工厂方法生成对象) -> 属性填充(populateBean,处理依赖注入) -> 初始化阶段(BeanPostProcessor、InitializingBean等回调) -> 放入singletonObjects缓存BeanFactory在这个链路里是总闸门。getBean的每一次调用,最终都会走到AbstractBeanFactory.doGetBean方法。这个方法里有一段核心逻辑:先从缓存里查(singletonObjects),查不到再看是不是FactoryBean生产的Bean,再没有则走完整的创建过程。我们平时说的三级缓存解决循环依赖,其实就发生在这个环节,而三级缓存的存储结构,就是BeanFactory实现类里维护的三个Map。
3. FactoryBean实战:当普通Bean满足不了你时
3.1 FactoryBean的定位:把复杂的对象创建逻辑收拢起来
如果说BeanFactory是容器本身,那FactoryBean就是容器里的一个“特殊工种”。Spring定义一个FactoryBean接口,意图非常明确:允许你用一个Bean去生产另一个对象。这个对象可能是动态代理、可能是远程服务客户端、可能是配置中心拉下来的数据源连接,总之,创建逻辑太复杂或者不能简单靠构造器搞定的场景,都可以包一层FactoryBean。
接口定义极简:
public interface FactoryBean<T> { // 返回工厂生产的对象实例 T getObject() throws Exception; // 返回对象的类型 Class<?> getObjectType(); // 是否为单例 boolean isSingleton(); }注意,这三个方法一个都不能省。getObjectType尤其重要,因为Spring在做类型匹配的时候(比如@Autowired按类型注入),需要提前知道这个FactoryBean能产出什么类型。如果你getObjectType返回null,可能导致类型推断失败,进而引发注入异常。
3.2 产品级案例:手写一个代理对象的FactoryBean
光讲概念没意思,我直接给你一个能落地的场景。假设我们要做一个基于JDK动态代理的接口增强功能,让某个RPC接口在调用时自动打印日志、记录耗时。正常情况下你可以在配置类里写@Bean方法返回代理对象,但这类逻辑往往会在多个地方复制。用FactoryBean封装一次,以后要扩展新接口,只需要配置一个Bean。
先定义一个接口:
public interface OrderService { String queryOrder(String orderId); }再写代理逻辑:
public class OrderServiceProxyFactory implements FactoryBean<OrderService> { @Override public OrderService getObject() { return (OrderService) Proxy.newProxyInstance( getClass().getClassLoader(), new Class<?>[]{OrderService.class}, (proxy, method, args) -> { long start = System.currentTimeMillis(); // 这里可以扩展真实的远程调用或本地降级逻辑 Object result = "模拟返回,orderId=" + args[0]; System.out.println("方法耗时: " + (System.currentTimeMillis() - start)); return result; } ); } @Override public Class<?> getObjectType() { return OrderService.class; } @Override public boolean isSingleton() { return true; } }然后是关键的注册环节。注意,如果你写成下面这样,是拿不到代理对象的:
@Configuration public class ProxyConfig { // 错误示范:直接给容器一个FactoryBean实例 @Bean public OrderServiceProxyFactory orderServiceProxyFactory() { return new OrderServiceProxyFactory(); } }这样注册之后,容器里确实有一个Bean叫orderServiceProxyFactory,但它的类型是OrderServiceProxyFactory,而不是OrderService。此时你需要费劲去getBean("&orderServiceProxyFactory")才能拿到工厂,再手动调getObject()拿到代理——非常不优雅。
正确的姿势是用@Bean方法直接返回代理对象,或者用@Bean方法声明返回FactoryBean时配合&前缀使用。我个人更推荐这样:
@Configuration public class ProxyConfig { @Bean("orderService") public OrderService orderService() { return new OrderServiceProxyFactory().getObject(); } }但如果是基于自动配置的框架组件,工厂模式经常会直接注册FactoryBean而不是生产后的对象。比如我们在写自己的Starter时,通常会往容器里扔一个FactoryBean,消费者直接按类型注入,根本不感知工厂的存在。这个差异要注意:你自己写业务代码时,@Bean返回生产后的对象最直观;做框架封装时,注册FactoryBean才更灵活。
3.3 测试FactoryBean的获取行为:&前缀的真相
前面提到BeanFactory接口的&前缀规则,这里用代码实测一遍。假设我已经按照错误示范注册了OrderServiceProxyFactory:
// 获取FactoryBean本身,需要加 & 前缀 Object factoryObj = context.getBean("&orderServiceProxyFactory"); System.out.println(factoryObj.getClass().getSimpleName()); // 输出:OrderServiceProxyFactory // 直接按名字获取,Spring发现该Bean是FactoryBean,自动调用getObject() Object productObj = context.getBean("orderServiceProxyFactory"); System.out.println(productObj.getClass().getSimpleName()); // 输出:jdk.proxy2.$Proxy 或者 com.sun.proxy.$Proxy重点是第二行输出。容器在doGetBean的过程中,拿到Bean实例后会做一个检查:
// AbstractBeanFactory.getBeanForObject 内部逻辑 if (bean instanceof FactoryBean) { FactoryBean<?> factory = (FactoryBean<?>) bean; // 判断是否需要返回工厂本身 if (shouldExportBean(name)) { return factory.getObject(); } }这就是为什么你直接getBean("orderServiceProxyFactory")拿到的不是工厂类,而是代理对象。Spring帮你自动拆了一层。
顺便提一个坑:如果你的业务Bean本身就需要是FactoryBean类型的实例,比如你想持有这个工厂去做特殊处理,记得在getBean时加&。这个细节在处理第三方库集成时会遇到,尤其是那些把FactoryBean注册进容器的框架,比如MyBatis的MapperFactoryBean、Shiro的SecurityManagerFactoryBean。
4. 二者关系深度理解:别名、类型推断与延迟加载
4.1 一张思维导图之外的逻辑:容器怎么区分普通Bean和工厂Bean
很多人看过那种把Spring容器画成一个Map,里面塞了一堆Bean的图。这个图过于简化了。真实情况是,DefaultListableBeanFactory里除了beanDefinitionMap,还有一个factoryBeanObjectCache,专门缓存FactoryBean生产出来的单例对象。
你说:“那getBean的时候,Spring怎么知道name对应的是普通Bean还是FactoryBean?”这里有一段非常核心的代码逻辑,我直接简化给你看。
第一步:先根据name从beanDefinitionMap里拿BeanDefinition,如果拿不到,可能是别名,需要通过aliasMap解析。
第二步:查看BeanDefinition里是否标注了factoryBeanName(在XML时代,可以通过factory-bean属性指定由某个工厂Bean来创建)。Spring源码里有一个专门的常量:
String FACTORY_BEAN_PREFIX = "&";第三步:如果目标Bean本身是个FactoryBean实现,Spring会先实例化这个工厂类,然后调用它的getObject()方法,把返回值作为真正的bean塞进容器缓存。
所以你可以把FactoryBean理解成一个“中间商”:容器负责创建并管理中间商,但对外暴露的是中间商提供的商品。BeanFactory则是“市场管理方”,它既不生产商品也不当中间商,它只管规则。
4.2 类型匹配时的隐藏问题:getObjectType影响自动注入
在字段注入或构造器注入时,Spring需要根据类型找Bean。比如:
@Autowired private OrderService orderService;容器会调用getType("orderService")来确定bean的类型是否匹配。而getType的实现里有一条分岔路:如果name对应的实例是FactoryBean,则通过getObjectType()返回值作为类型;如果getObjectType()返回null,Spring就要走getObject()来硬着头皮确定类型。
某些场景下getObject()是有副作用的,比如它会真实建立数据库连接、外部网络通信等。为了避免这种启动期的意外开销,我强烈建议你在实现FactoryBean时,把getObjectType()写得明确一点。这也是很多框架源码里一个不起眼但是很重要的优化细节。
4.3 延迟加载和FactoryBean的相互作用
Spring的@Lazy懒加载语义同样适用于FactoryBean。默认情况下,单例Bean在容器启动阶段就会被实例化,FactoryBean本身也会被实例化,但它的getObject()方法是等到有人调用getBean时才触发执行(除非这个Bean是某个@Autowired的依赖)。
我实践里的经验是:如果你的FactoryBean的getObject()开销很大(比如加载机器学习模型、初始化分布式锁客户端),不要依赖默认行为,建议显式加上@Lazy修饰,或者干脆把创建逻辑移到第一次使用时再触发。
5. 注册方式演进:从XML到JavaConfig再到自动配置
5.1 XML时代的自定义FactoryBean
在SpringBoot还没铺开的时候,注册一个自定义FactoryBean最常见的方式是XML:
<bean id="orderService" class="com.example.OrderServiceProxyFactory"/>注意,这样写出来的Bean类型是生产后的OrderService,因为Spring会识别class是FactoryBean,自动走getObject()。如果想拿工厂本身,则需要:
<bean id="&orderService" class="com.example.OrderServiceProxyFactory"/>这里的&在XML里必须转义为&,很多人当年就是被这个细节坑过,配置写错了导致IDEA飘红,还以为是XML解析问题。
5.2 JavaConfig和自动配置里的新写法
SpringBoot时代,你大概率不会手动new一个FactoryBean注册,而是通过自动配置类。拿一个典型的场景——如果你的项目里集成了某个配置中心的客户端,你往往会在依赖里引入一个Starter,这个Starter内部通常就是用FactoryBean来封装复杂初始化逻辑的。
比如自动配置类中这样写:
@Bean @ConditionalOnMissingBean public ConfigCenterClientFactoryBean configCenterClientFactoryBean() { return new ConfigCenterClientFactoryBean(); }然后使用方只需要:
@Autowired private ConfigCenterClient configCenterClient;因为FactoryBean生产出来的对象类型正好是ConfigCenterClient,类型匹配直接通过,使用方完全感知不到工厂的存在。
5.3 认知升级:FactoryBean与@Bean方法的取舍
那有人问了:“我直接用@Bean方法返回对象不就行了,为什么非要FactoryBean?”这个问题问得很好。@Bean方法适合创建逻辑简单、依赖清晰的对象。但你考虑这些场景:
- 需要根据环境配置动态选择代理策略(JDK代理还是CGLIB)
- 需要统计工厂创建次数做性能监控
- 需要让同一个工厂生产多种相关对象(通过不同name注册多个FactoryBean实例,各自的getObject返回不同配置)
- 需要结合自动配置实现可插拔扩展
在这些场景下,FactoryBean的组合能力更强。实际上Spring内置了一堆有用的FactoryBean,比如:
- JndiObjectFactoryBean:从JNDI获取外部资源
- LocalContainerEntityManagerFactoryBean:构建JPA的EntityManagerFactory
- SqlSessionFactoryBean(MyBatis-Spring核心):构建SqlSessionFactory
- 各种XxxProxyFactoryBean(Spring AOP老版本常用)
你看看,框架层面对FactoryBean的依赖有多深。所以理解FactoryBean不是冷门知识,而是读到Spring核心框架和主流整合源码的必备前提。
6. 从BeanFactory源码看Bean的生命周期钩子点
6.1 doGetBean的完整流程拆解
既然标题里点名了BeanFactory,我建议你至少亲手打开AbstractBeanFactory类,把doGetBean方法完整看一遍。我帮你把代码里的关键节点抽出来,整理成一个便于记忆的清单:
- 第一步:解析name,去掉
&前缀,如果是&开头获取工厂本身,则后面走过的地方会不一样 - 第二步:尝试从单例缓存拿(getSingleton方法内部涉及三级缓存)
- 第三步:缓存没有,检查父容器(HierarchicalBeanFactory的parentBeanFactory)
- 第四步:确认当前BeanDefinition不是抽象的(abstract=true的配置模板是不直接实例化的)
- 第五步:处理依赖的Bean(@DependsOn对应的方法)
- 第六步:根据scope(单例/原型/其他自定义Scope)选择创建策略
- 第七步:创建完成后,调用所有注册的BeanPostProcessor(这里最典型的是AutowiredAnnotationBeanPostProcessor)
- 第八步:如果Bean实现了InitializingBean,则调用afterPropertiesSet;再调用initMethod
- 第九步:放入单例缓存
每一步都有对应的钩子方法和扩展点。理解了这条线,以后你排查循环依赖、Bean前置初始化、后置代理增强等问题,就有明确的排查路径了。
6.2 循环依赖为什么经常和FactoryBean扯上关系
介绍一个真实踩坑场景:在项目里搞了个自定义FactoryBean,这个工厂的getObject()里又需要注入另一个Bean,而那个Bean反向依赖了工厂生产出来的对象。结果就是启动报错:BeanCurrentlyInCreationException。
循环依赖的解决范围是有限的。Spring的三级缓存只支持单例模式下的“早期引用暴露”,而且对FactoryBean生产出来的对象,并不是像普通Bean那样在实例化后就暴露。FactoryBean本身在创建过程中如果依赖了其他Bean,那个Bean又依赖FactoryBean生成的东西,就可能绕不过循环依赖的解决机制。
我的建议:不要在FactoryBean的getObject()里注入那些会转头依赖该FactoryBean产物的Bean。如果生产逻辑确实需要依赖其他组件,可以改用ObjectProvider延迟获取,或者把依赖拆到另一个组件里,由外部协调。
6.3 单例池与工厂缓存的实际存储结构
搞清楚BeanFactory底层存储结构,对你理解Spring并发和多线程安全问题很有帮助。DefaultListableBeanFactory的一个核心字段是:
/** Cache of singleton objects: bean name --> bean instance */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);注意这个ConcurrentHashMap的初始容量是256,内部以ConcurrentHashMap保证线程安全。当多个线程同时getBean同一个单例时,Spring通过synchronized锁块保证单例池的互斥写入。
而FactoryBean生产对象的缓存,实际上是放在另一个类里的——FactoryBeanRegistrySupport类里有一个字段:
private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>(16);这俩缓存的分离是有讲究的:普通Bean直接存单例池,FactoryBean生产出来的对象存在独立缓存。这样设计,一方面避免把FactoryBean和它的产品混淆,另一方面也方便支持&前缀获取工厂本身。你如果只记住了“Spring Bean都在一个Map里”,以后看这些源码很容易被绕晕。
7. 常见问题与排查技巧实录
7.1 NoSuchBeanDefinitionException的四种可能
这个异常是Spring开发中出现频率最高的报错之一,但同一种报错背后,原因可能完全不一样。我整理一份速查:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 按名字getBean失败 | Bean name写错或没扫描到 | 检查@ComponentScan路径和name拼写 |
| 按类型注入失败 | 存在多个同类型Bean且未指定@Primary/@Qualifier | 加@Primary或@Qualifier指定 |
| FactoryBean生产失败 | getObject()抛异常或返回null | 打断点看getObject()内部逻辑 |
| 升级SpringBoot后突然失败 | 版本升级后FactoryBean的泛型推断逻辑变了 | 检查getObjectType是否还返回正确类型 |
7.2 一个真实案例:getObjectType返回null导致的类型匹配失败
之前做过一个项目,某模块集成外部配置中心,封装了一个ExternalConfigFactoryBean,开发同学图省事,getObjectType直接写成:
@Override public Class<?> getObjectType() { return null; }启动时一切正常,因为没人按类型注入它。后来在另一个模块里加了自动装配:
@Autowired private ExternalConfig externalConfig;结果启动直接报:No qualifying bean of type 'ExternalConfig' available。排查半天,发现就是getObjectType返回null导致的。把getObjectType改成返回ExternalConfig.class之后,一切恢复正常。
教训就是:getObjectType的返回值不是可有可无的,它在容器类型匹配阶段起着决定性作用。
7.3 排查FactoryBean问题的独门手法
如果你的项目里FactoryBean比较多,我推荐你用下面这个临时端点来梳理容器里的Bean关系。写一个简单的ApplicationRunner:
@Component public class BeanInventoryRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { ConfigurableListableBeanFactory factory = ...; String[] names = factory.getBeanDefinitionNames(); for (String name : names) { Object bean = factory.getBean(name); boolean isFactoryBean = bean instanceof FactoryBean; System.out.println(name + " -> " + bean.getClass() + " [FactoryBean: " + isFactoryBean + "]"); } } }注意,这里如果bean是FactoryBean类型,打印出来的是工厂本身,因为在Spring的内部逻辑里,没有加&时getBean返回的是生产后的对象。为了准确判断,你可以额外用factory.isTypeMatch(name, FactoryBean.class)来检查。这个调试思路,能帮你快速摸清一个陌生项目的容器里到底注册了什么。
7.4 别把原型Bean的工厂缓存搞成脏数据
最后一个提醒:当你把FactoryBean的isSingleton()设为false时,意味着每次getBean都会新建对象,此时factoryBeanObjectCache这个单例缓存不会生效。但Spring源码里有个明细行为很多人没注意——isSingleton()返回false的FactoryBean,如果要手动管理生命周期,需要实现DisposableBean。如果你生产的对象有连接、线程池等资源需要关闭,记得提供destroy方法,并且让生产的对象实现相关Lifecycle接口,否则项目关闭时资源释放可能不干净。
8. 从BeanFactory到FactoryBean:我对这套设计的一点体会
说实话,最初读Spring源码的时候,我对BeanFactory和FactoryBean这套命名是有点烦躁的——俩名字就差一个单词,含义却是两个方向。踩过几次坑之后,我反而觉得这样的命名很巧妙:BeanFactory代表的是“工厂的工厂”,FactoryBean代表的是“能被当作工厂的Bean”。它们一个在容器上层,一个在对象创建层,一起撑起了Spring最核心的实例化机制。
如果让我给你几个学习建议:
第一,先用起来,再抠源码。不要一上来就去读DefaultListableBeanFactory那几千行代码,先自己写一个FactoryBean,感受一下&前缀的差异,再回头读源码,很多疑点会自动解开。
第二,多关注框架源码里的FactoryBean使用方式。比如MyBatis-Spring的MapperFactoryBean,你弄懂这一个,几乎所有类似的中间件整合思路都能触类旁通。核心就一句话:让容器负责创建工厂,让工厂负责生产对象,分工明确。
第三,别被“BeanFactory被ApplicationContext取代”这种说法误导。ApplicationContext只是封装了BeanFactory,底层的规则从未变过。理解BeanFactory的接口设计,就是理解了Spring容器的最小公约数。
就写到这里,如果你在实践里遇到Bean创建的诡异问题,欢迎在评论里聊聊你的启动日志和Bean定义,我看到了会尽量答复。