资讯动态

BeanFactory与FactoryBean彻底讲透:Spring IoC容器核心机制与实战解析

发布时间:2026/10/17 5:19:13 来源:尧图企业网站定制
1. 从一段报错开始为什么说BeanFactory是Spring的地基写Spring的老哥老姐们肯定都见过这套组合拳[ClassPathXmlApplicationContext] / [AnnotationConfigApplicationContext] / [FileSystemXmlApplicationContext]拿来就newnew完就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, ClassT requiredType) throws BeansException; // 获取该Bean的实例化类型FactoryBean则返回生产的对象类型 Class? getType(String name) throws NoSuchBeanDefinitionException; // 是不是单例对应scopesingleton boolean isSingleton(String name) throws NoSuchBeanDefinitionException; // 是不是原型对应scopeprototype 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等一堆接口同时还维护了beanDefinitionMapBean定义注册表、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 FactoryBeanT { // 返回工厂生产的对象实例 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 FactoryBeanOrderService { 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()返回nullSpring就要走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最常见的方式是XMLbean idorderService classcom.example.OrderServiceProxyFactory/注意这样写出来的Bean类型是生产后的OrderService因为Spring会识别class是FactoryBean自动走getObject()。如果想拿工厂本身则需要bean idorderService classcom.example.OrderServiceProxyFactory/这里的在XML里必须转义为amp;很多人当年就是被这个细节坑过配置写错了导致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的EntityManagerFactorySqlSessionFactoryBeanMyBatis-Spring核心构建SqlSessionFactory各种XxxProxyFactoryBeanSpring AOP老版本常用你看看框架层面对FactoryBean的依赖有多深。所以理解FactoryBean不是冷门知识而是读到Spring核心框架和主流整合源码的必备前提。6. 从BeanFactory源码看Bean的生命周期钩子点6.1 doGetBean的完整流程拆解既然标题里点名了BeanFactory我建议你至少亲手打开AbstractBeanFactory类把doGetBean方法完整看一遍。我帮你把代码里的关键节点抽出来整理成一个便于记忆的清单第一步解析name去掉前缀如果是开头获取工厂本身则后面走过的地方会不一样第二步尝试从单例缓存拿getSingleton方法内部涉及三级缓存第三步缓存没有检查父容器HierarchicalBeanFactory的parentBeanFactory第四步确认当前BeanDefinition不是抽象的abstracttrue的配置模板是不直接实例化的第五步处理依赖的BeanDependsOn对应的方法第六步根据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 MapString, Object singletonObjects new ConcurrentHashMap(256);注意这个ConcurrentHashMap的初始容量是256内部以ConcurrentHashMap保证线程安全。当多个线程同时getBean同一个单例时Spring通过synchronized锁块保证单例池的互斥写入。而FactoryBean生产对象的缓存实际上是放在另一个类里的——FactoryBeanRegistrySupport类里有一个字段private final MapString, 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关系。写一个简单的ApplicationRunnerComponent 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定义我看到了会尽量答复。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑