01-03-Spring-IOC-Bean-Creation
Spring IOC 专题:Bean 创建流程源码深度解析
本文基于 Spring Boot
3.3.9、Spring Framework6.1.17、Java17。建议先完成前置学习:
01-01-Spring-IOC.md和01-02-Spring-IOC-ConfigurationClassPostProcessor.md。前两篇解决“BeanDefinition 从哪里来”,本文集中解决“BeanDefinition 如何变成最终可使用的 Bean”。
一、先建立完整认知
1.1 Bean 创建到底在解决什么问题
配置类解析完成后,容器中保存的是 BeanDefinition,不是业务对象。
1 | 注解 / XML / 编程式注册 |
Bean 创建流程需要同时解决:
- 应该使用哪个构造器、工厂方法或 Supplier 创建对象
- 构造器参数和字段依赖从哪里获取
@Autowired、@Value、@Resource在什么阶段执行Aware、@PostConstruct、InitializingBean的执行顺序- Bean 何时被 AOP 包装成代理
- singleton、prototype、自定义 Scope 如何创建
FactoryBean返回的是工厂还是产品对象- 单例循环依赖如何暴露早期引用
- 创建失败后如何清理缓存和依赖关系
- 容器关闭时如何执行销毁回调
1.2 一句话主线
doGetBean()决定“从哪里得到 Bean”,createBean()决定“是否允许后处理器提前替换 Bean”,doCreateBean()完成“实例化、注入、初始化和销毁注册”。
1.3 必须背下来的调用链
1 | AbstractApplicationContext.refresh() |
1.4 三组容易混淆的概念
| 概念 | 含义 |
|---|---|
| 实例化 Instantiation | 通过构造器、工厂方法或 Supplier 得到原始 Java 对象 |
| 属性填充 Population | 为字段、Setter、属性和方法参数解析并注入依赖 |
| 初始化 Initialization | 执行 Aware、BeanPostProcessor、@PostConstruct、初始化方法和代理增强 |
1 | 实例化 != 初始化 |
二、使用最小案例贯穿全文
2.1 示例代码
1 | @Service |
如果不使用 @Service 扫描,而改为通过配置类注册并指定自定义初始化方法:
1 | @Bean(initMethod = "customInit") |
同一个类不要同时使用 @Service 和上述 @Bean 注册,否则会得到两个不同的 BeanDefinition。
后续需要持续观察四个对象:
1 | BeanDefinition:描述如何创建 OrderService |
2.2 推荐的调试配置
1 | spring: |
Spring Boot 3.3.9 默认禁止循环依赖。只有研究循环依赖实验时才建议临时开启该配置。
三、Bean 创建从哪里开始
3.1 refresh() 中的入口
普通非懒加载单例主要在下面的阶段创建:
1 | AbstractApplicationContext.refresh() |
finishBeanFactoryInitialization() 并不是唯一会触发 Bean 创建的地方。下面这些操作也会触发:
- 用户主动调用
applicationContext.getBean() - 创建其他 Bean 时递归解析依赖
BeanFactoryPostProcessor阶段错误地提前调用getBean()- 非懒 Bean 依赖某个懒 Bean
@DependsOn要求先创建指定 Bean- 获取
FactoryBean的产品对象
3.2 preInstantiateSingletons() 做了什么
Spring Framework 6.1.17 中,DefaultListableBeanFactory.preInstantiateSingletons() 的核心逻辑是:
1 | public void preInstantiateSingletons() throws BeansException { |
关键点:
- 只主动预实例化非抽象、singleton、非 lazy 的 Bean
- prototype 不会在这里批量创建
- 普通
FactoryBean先创建工厂本身,即getBean("&beanName") SmartFactoryBean.isEagerInit()返回 true 时才提前创建产品对象SmartInitializingSingleton在所有普通单例创建完成后执行,适合做全局单例就绪后的工作
3.3 lazy Bean 是否永远不创建
不是。
1 | @Lazy |
它只是跳过 preInstantiateSingletons() 的主动创建。如果出现以下情况,仍会提前创建:
- 被非懒单例直接注入
- 被
@DependsOn引用 - 用户主动调用
getBean() - 某些基础设施逻辑主动查询并实例化它
如果希望注入点本身也保持懒加载,需要在注入点使用 @Lazy、ObjectProvider 或其他延迟获取机制。
四、getBean() 与 doGetBean() 主流程
4.1 getBean() 只是入口
常见重载最终都会进入:
1 | protected <T> T doGetBean( |
doGetBean() 的职责不是单纯“new 一个对象”,而是统一处理:
- Bean 名称转换和别名
- 单例缓存命中
FactoryBean解引用- 父子容器委派
- BeanDefinition 合并
@DependsOn- singleton、prototype、自定义 Scope
- 类型适配
4.2 第一步:转换 Bean 名称
1 | String beanName = transformedBeanName(name); |
它会处理:
- 别名转换
&前缀去除- FactoryBean 名称规范化
1 | getBean("userService") → 获取普通 Bean 或 FactoryBean 产品 |
4.3 第二步:优先查询单例缓存
1 | Object sharedInstance = getSingleton(beanName); |
为什么缓存命中后还要调用 getObjectForBeanInstance()?
因为缓存中的对象可能是 FactoryBean,用户真正想要的是它的 getObject() 产品,而不是工厂本身。
4.4 第三步:prototype 循环依赖检查
1 | if (isPrototypeCurrentlyInCreation(beanName)) { |
prototype 每次获取都创建新实例,没有单例缓存和早期暴露机制,因此 Spring 无法使用三级缓存解决 prototype 循环依赖。
4.5 第四步:父容器委派
1 | BeanFactory parentBeanFactory = getParentBeanFactory(); |
规则是:
- 当前容器存在本地 BeanDefinition:使用当前容器
- 当前容器不存在:尝试父容器
- 子容器可以覆盖父容器同名 Bean
- 父容器不能反向获取子容器 Bean
4.6 第五步:标记 Bean 即将创建
1 | if (!typeCheckOnly) { |
这一步会影响 merged BeanDefinition 缓存。如果一个 BeanDefinition 在创建前被修改,Spring 需要重新合并,而不是继续使用旧缓存。
4.7 第六步:获取合并后的 RootBeanDefinition
1 | RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); |
后续创建流程统一使用 RootBeanDefinition,因为它已经综合了:
- 父 BeanDefinition 配置
- 当前 BeanDefinition 覆盖项
- scope、lazy、dependsOn
- 构造器参数和属性值
- factoryBeanName、factoryMethodName
- initMethod、destroyMethod
- methodOverrides
4.8 第七步:先创建 dependsOn Bean
1 | String[] dependsOn = mbd.getDependsOn(); |
@DependsOn 表示创建顺序和销毁顺序依赖,不等同于属性注入。
1 | @Component |
Spring 还会检测 dependsOn 形成的显式循环,并抛出 BeanCreationException。
4.9 第八步:根据 Scope 创建
singleton
1 | sharedInstance = getSingleton(beanName, () -> { |
同一个 BeanFactory 内只创建一次,并由单例注册表管理。
prototype
1 | beforePrototypeCreation(beanName); |
每次 getBean() 都创建新实例,容器不负责完整销毁生命周期。
自定义 Scope
1 | Scope scope = this.scopes.get(scopeName); |
request、session 等作用域的实例保存位置由对应 Scope 实现决定。
4.10 doGetBean() 流程图
1 | doGetBean(name) |
五、BeanDefinition 合并与依赖关系
5.1 为什么需要 merged BeanDefinition
Spring 支持 BeanDefinition 继承:
1 | <bean id="baseService" abstract="true"> |
即使主要使用注解配置,Spring 内部也统一转换为 RootBeanDefinition,让后续创建流程不再区分原始定义类型。
5.2 合并结果什么时候缓存
getMergedLocalBeanDefinition() 会缓存合并结果,避免每次 getBean() 重复计算。
但在真正创建之前,markBeanAsCreated() 会确保被修改过的 BeanDefinition 能重新合并。
5.3 dependentBeanMap 与 dependenciesForBeanMap
Spring 会维护两个方向的依赖关系:
1 | dependentBeanMap: |
用途包括:
- 检测
@DependsOn循环 - 创建失败时清理依赖 Bean
- 容器关闭时先销毁依赖方,再销毁被依赖方
- 判断早期引用是否已被其他 Bean 使用
六、单例创建器 getSingleton()
6.1 两个同名方法承担不同职责
1 | getSingleton(beanName) |
用于查询一级、二级、三级缓存。
1 | getSingleton(beanName, singletonFactory) |
用于保证一个单例只被创建一次,并管理创建前后状态、异常清理和最终入池。
6.2 真正创建单例的模板
1 | public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { |
6.3 beforeSingletonCreation()
1 | protected void beforeSingletonCreation(String beanName) { |
它把 Bean 名称加入“正在创建集合”。重复加入通常意味着出现了无法通过早期引用处理的创建路径。
6.4 创建失败为什么必须清理
创建过程中可能已经产生:
- 三级缓存 ObjectFactory
- 二级缓存早期引用
- dependentBeanMap 依赖记录
- 销毁回调
- 被递归创建的其他单例
如果失败后不清理,下一次创建会读到污染状态。doGetBean() 中创建单例的回调捕获到 BeansException 后会调用 destroySingleton(beanName) 清理缓存和销毁信息;getSingleton() 同时会收集 suppressed exceptions,作为 BeanCreationException 的关联原因帮助定位根因。
6.5 addSingleton() 的最终动作
1 | protected void addSingleton(String beanName, Object singletonObject) { |
Bean 进入一级缓存时,二级和三级缓存中的同名数据必须删除。
七、createBean():正式创建前的最后一道门
7.1 createBean() 的核心职责
1 | protected Object createBean( |
可以把它理解成两步:
- 准备 BeanClass、方法覆盖等创建元数据。
- 给
InstantiationAwareBeanPostProcessor一次“绕过默认创建流程”的机会。
7.2 resolveBeforeInstantiation()
调用链:
1 | resolveBeforeInstantiation() |
如果某个后处理器在实例化前直接返回对象,Spring 将不会执行:
doCreateBean()- 默认构造器或工厂方法实例化
populateBean()- 普通初始化流程
这是一种“短路创建”。
7.3 它是否就是普通 AOP 代理入口
不完全是。
普通基于 Bean 方法匹配的 AOP 代理,通常在 postProcessAfterInitialization() 中创建。AbstractAutoProxyCreator.postProcessBeforeInstantiation() 主要用于自定义 TargetSource 等可以提前确定代理目标的特殊场景。
面试时不要简单回答:“所有 AOP 代理都在实例化前创建”。
八、doCreateBean():Bean 创建的核心模板
8.1 源码主线
1 | protected Object doCreateBean( |
8.2 八个关键阶段
1 | 1. createBeanInstance() 创建原始对象 |
8.3 为什么先解析注入元数据再暴露早期引用
MergedBeanDefinitionPostProcessor 会提前分析 BeanClass,例如:
- 哪些字段标注了
@Autowired - 哪些方法需要依赖注入
- 哪些成员标注了
@Resource - 哪些销毁方法需要注册
这些结果会被缓存。后续 populateBean() 只需要执行注入,不必重复扫描整个类。
8.4 为什么注册销毁回调时传入原始 bean
销毁适配器需要识别目标类上的:
@PreDestroyDisposableBean- 自定义 destroy-method
代理对象可能隐藏目标对象的具体销毁方法,因此 Spring 会结合原始对象、BeanDefinition 和后处理器保存销毁信息。
九、createBeanInstance():到底怎样实例化对象
9.1 实例化策略总览
Spring Framework 6.1.17 的主要分支顺序如下:
1 | createBeanInstance() |
9.2 Supplier 实例化
1 | RootBeanDefinition bd = new RootBeanDefinition(OrderService.class); |
对应源码入口:
1 | obtainFromSupplier(instanceSupplier, beanName, mbd); |
Supplier 主要用于编程式 BeanDefinition 注册,不等同于普通 @Bean 方法。
9.3 @Bean 工厂方法实例化
1 | @Configuration |
对应 BeanDefinition 元数据:
1 | factoryBeanName = appConfig |
调用链:
1 | createBeanInstance() |
静态工厂方法没有 factoryBeanName,直接在 BeanClass 上调用静态方法。
9.4 默认构造器实例化
如果没有 Supplier、工厂方法、需要自动装配的构造器或显式构造参数,最终进入:
1 | instantiateBean(beanName, mbd); |
默认使用 InstantiationStrategy:
1 | SimpleInstantiationStrategy |
9.5 BeanWrapper 的作用
实例化结果不是直接返回对象,而是包装为 BeanWrapper:
1 | BeanWrapper bw = new BeanWrapperImpl(beanInstance); |
它负责:
- 保存包装对象
- 获取属性描述符
- 类型转换
- 设置属性值
- 支持嵌套属性路径
- 集成 ConversionService 和 PropertyEditor
十、构造器选择与参数解析
10.1 单构造器为什么可以不写 @Autowired
1 | @Service |
当类只有一个非默认构造器时,AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors() 会把它作为候选构造器。
10.2 多构造器规则
1 | @Service |
核心规则:
- 可以存在多个
@Autowired(required = false)构造器 - 最多只能有一个 required 构造器
- required 构造器存在时,不允许再声明其他
@Autowired构造器 - 有默认构造器时,它可以作为非 required 候选的回退
- 没有
@Autowired且只有一个带参构造器时,使用该构造器
10.3 ConstructorResolver 如何选择最佳构造器
候选构造器不会简单按参数数量匹配,核心过程是:
1 | 1. 收集候选构造器 |
10.4 贪婪构造器不等于无条件选择参数最多的
参数更多的构造器只有在所有依赖都能够解析、类型转换成功且权重更优时才会被选中。
1 | @Autowired(required = false) |
如果 MissingService 不存在且第一个构造器不是 required,Spring 可以继续尝试参数较少的候选构造器。
10.5 构造器循环依赖为什么无法解决
1 | 创建 A |
三级缓存的放入时机在 createBeanInstance() 之后,因此构造器执行完成前不存在可暴露的 A 对象。
十一、MergedBeanDefinitionPostProcessor
11.1 为什么需要这个阶段
实例化完成后,Spring 已经知道真实 Bean 类型,可以让后处理器解析类级元数据:
1 | applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); |
典型实现:
| 实现 | 主要职责 |
|---|---|
AutowiredAnnotationBeanPostProcessor |
解析 @Autowired、@Value 字段和方法 |
CommonAnnotationBeanPostProcessor |
解析 @Resource 等通用注解 |
InitDestroyAnnotationBeanPostProcessor |
解析 @PostConstruct、@PreDestroy |
11.2 此时只是解析,不是注入
以 AutowiredAnnotationBeanPostProcessor 为例:
1 | postProcessMergedBeanDefinition() |
真正注入发生在后面的 postProcessProperties()。
11.3 为什么元数据要缓存
反射扫描字段和方法成本较高。对于 prototype Bean,如果每次创建都重新扫描,会产生额外开销。
缓存通常以 beanName 或目标 Class 为键;类发生变化或缓存失效时才重新构建。
十二、populateBean():属性填充与依赖注入
12.1 完整顺序
1 | populateBean() |
12.2 postProcessAfterInstantiation() 可以阻止注入
1 | if (!bp.postProcessAfterInstantiation(bean, beanName)) { |
这是框架级扩展点。返回 false 后,Spring 不再继续普通属性填充。
业务项目一般不应随意使用,否则容易造成字段为空且排查困难。
12.3 byName 与 byType 是 BeanDefinition 级自动装配
1 | mbd.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME); |
它们与 @Autowired 不是同一套入口:
- byName / byType 由 BeanDefinition 的 autowireMode 驱动
@Autowired由AutowiredAnnotationBeanPostProcessor驱动
现代注解项目主要使用后者。
12.4 @Autowired 字段注入
1 | AutowiredAnnotationBeanPostProcessor.postProcessProperties() |
因此字段注入一定发生在构造器执行之后。
12.5 @Autowired 方法注入
1 | @Autowired |
Spring 会分别解析每个方法参数,然后通过反射调用该方法。
Setter 注入只是方法注入的一种常见形式,方法名称不必须以 set 开头。
12.6 @Value 的本质
1 | @Value("${order.timeout:3000}") |
它仍然通过 DependencyDescriptor 解析,只是候选值来自:
1 | AutowireCandidateResolver.getSuggestedValue() |
12.7 applyPropertyValues()
BeanDefinition 中显式配置的属性最终在这里写入:
1 | MutablePropertyValues |
十三、resolveDependency():依赖到底怎样找到
13.1 doResolveDependency() 五步
Spring Framework 6.1.17 的主线:
1 | Step 1:descriptor.resolveShortcut() |
13.2 findAutowireCandidates()
它先找到目标类型的候选 Bean 名称,然后逐个判断:
- 不能把当前 Bean 自己作为普通依赖注入给自己
- 泛型
ResolvableType是否匹配 AutowireCandidateResolver.isAutowireCandidate()是否允许@Qualifier是否匹配- 是否为 fallback 候选
- 是否来自父容器
13.3 多候选的消歧顺序
Spring Framework 6.1.17 的 determineAutowireCandidate() 顺序是:
1 | 1. @Primary |
@Qualifier 通常在进入此步骤前,就已经参与候选过滤。
13.4 示例
1 | public interface PaymentService { |
1 | @Autowired |
使用 @Primary Bean。
1 | @Autowired |
候选过滤后只剩支付宝实现。
13.5 集合注入
1 | @Autowired |
流程:
1 | resolveMultipleBeans() |
可以通过 @Order、Ordered 或 @Priority 影响集合顺序。
13.6 ObjectProvider 为什么能延迟创建
1 | private final ObjectProvider<ReportService> provider; |
注入时得到的是 Provider,不一定立即调用 getBean(ReportService)。只有执行:
1 | provider.getObject(); |
才会进一步解析目标 Bean。
十四、initializeBean():初始化与代理增强
14.1 精确调用顺序
1 | protected Object initializeBean( |
14.2 Aware 回调分成两组
第一组由 invokeAwareMethods() 直接调用:
1 | BeanNameAware |
第二组由 ApplicationContextAwareProcessor 在初始化前阶段调用:
1 | EnvironmentAware |
14.3 @PostConstruct 在哪里执行
@PostConstruct 不在 invokeInitMethods() 中执行。
它由 InitDestroyAnnotationBeanPostProcessor 的初始化前回调触发:
1 | applyBeanPostProcessorsBeforeInitialization() |
这里的 invokeInitMethods() 是后处理器自己的方法,不是 AbstractAutowireCapableBeanFactory.invokeInitMethods(),两者不要混淆。
14.4 Spring 初始化方法顺序
1 | 1. BeanNameAware / BeanClassLoaderAware / BeanFactoryAware |
14.5 自定义初始化方法
1 | @Bean(initMethod = "customInit") |
或 XML:
1 | <bean id="orderService" |
如果自定义方法名与 afterPropertiesSet() 相同,Spring 会避免无意义的重复调用。
14.6 为什么 @PostConstruct 中事务通常不生效
1 | @PostConstruct |
原因有两层:
this.createOrder()是对象内部调用,不经过代理@PostConstruct执行时最终代理通常尚未完成创建
如果初始化逻辑必须使用事务,考虑拆分到另一个 Bean,或在应用就绪事件之后通过代理调用。
十五、AOP 代理究竟在哪个阶段生成
15.1 普通场景
正常情况下:
1 | initializeBean() |
此时:
1 | bean = 原始对象 |
最终一级缓存中保存的是 exposedObject。
15.2 循环依赖场景为什么可能提前创建代理
如果 A 依赖 B,B 又依赖 A:
1 | A 实例化完成 |
15.3 getEarlyBeanReference()
1 | protected Object getEarlyBeanReference( |
AbstractAutoProxyCreator 是最重要的实现之一。它会记录该 Bean 已参与早期代理流程,避免初始化后再次创建另一个代理。
15.4 早期引用一致性检查
doCreateBean() 初始化完成后会再次检查:
1 | Object earlySingletonReference = getSingleton(beanName, false); |
主要处理两种情况:
情况一:初始化后没有产生新对象
1 | exposedObject == bean |
说明最终初始化没有再包装对象。如果之前存在早期代理,应把最终暴露对象替换为该早期代理。
情况二:初始化后产生新代理,但其他 Bean 已拿到原始对象
此时容器外部可能同时存在两个版本:
1 | B 持有原始 A |
默认情况下 Spring 会抛出 BeanCurrentlyInCreationException,而不是静默接受不一致引用。
十六、三级缓存与循环依赖完整推演
16.1 三级缓存定义
1 | // 一级缓存:完整单例 |
| 缓存 | 保存内容 | 进入时机 | 移除时机 |
|---|---|---|---|
| 一级缓存 | 最终单例对象 | Bean 完整创建成功后 | 容器销毁或创建失败清理 |
| 二级缓存 | 已生成的早期对象或代理 | 第一次真正请求早期引用时 | Bean 进入一级缓存时 |
| 三级缓存 | ObjectFactory |
原始对象实例化后、属性填充前 | 转入二级缓存或一级缓存时 |
16.2 查询缓存的真实条件
1 | protected Object getSingleton(String beanName, boolean allowEarlyReference) { |
只有 Bean 正在创建时,才会继续查询二级和三级缓存。正常使用一个已完成 Bean 时只需要查询一级缓存。
16.3 A、B 字段循环依赖时间线
假设:
1 | @Service |
并且已临时开启循环依赖:
1 | spring.main.allow-circular-references=true |
完整时间线:
1 | 1. getBean("a") |
16.4 为什么需要三级而不是二级
如果实例化完成后直接把原始对象放进二级缓存:
1 | B 获取到原始 A |
会产生引用不一致。
如果实例化完成后立即为所有 Bean 创建代理,也可以避免部分问题,但会导致:
- 没发生循环依赖的 Bean 也被过早判断代理
- 代理创建时机与正常生命周期不一致
- 某些后处理器尚未完成必要准备
- 无法做到“只有真正需要早期引用时才调用”
三级缓存保存 ObjectFactory,把“是否生成早期代理”的决定推迟到其他 Bean 真正请求该引用的时刻。
16.5 Spring 能解决哪些循环依赖
| 场景 | Framework 能力 | Boot 3.3.9 默认行为 |
|---|---|---|
| singleton 字段/Setter 循环 | 开启 allowCircularReferences 后可能解决 | 默认失败 |
| singleton + 普通 AOP 代理 | 早期代理机制可能解决 | 默认失败 |
| 构造器循环 | 无法通过三级缓存解决 | 失败 |
| prototype 循环 | 无法解决 | 失败 |
@Lazy 代理打断依赖 |
可以避免立即获取真实对象 | 可用 |
@Async 等晚期代理 |
可能出现早期对象与最终代理不一致 | 应重构 |
16.6 最佳实践
不要把三级缓存当作业务架构工具。
循环依赖通常意味着:
- 职责边界不清晰
- 双向调用过多
- 领域服务拆分不合理
- 缺少中介对象或事件机制
优先方案:
- 提取共同依赖到第三个服务。
- 使用领域事件或应用事件解耦。
- 将查询与写入职责拆开。
- 使用
ObjectProvider处理真正需要的延迟查找。 - 最后才考虑
@Lazy,不要直接开启全局循环依赖。
十七、FactoryBean 创建流程
17.1 FactoryBean 是什么
1 | public interface FactoryBean<T> { |
它本身是一个由 Spring 管理的 Bean,但它还能生产另一个对象。
17.2 两种获取方式
1 | context.getBean("myFactory"); |
返回 FactoryBean.getObject() 的产品。
1 | context.getBean("&myFactory"); |
返回 FactoryBean 工厂本身。
17.3 getObjectForBeanInstance()
1 | getObjectForBeanInstance(beanInstance, name, beanName, mbd) |
17.4 产品对象缓存
FactoryBean 产品有独立缓存:
1 | private final Map<String, Object> factoryBeanObjectCache; |
不要与 singletonObjects 混淆:
1 | singletonObjects["myFactory"] |
只有满足以下条件时,产品对象才会缓存:
- FactoryBean 自身是容器单例
FactoryBean.isSingleton()返回 true
17.5 产品对象也会执行后处理器
1 | FactoryBean.getObject() |
产品对象通常不会走完整的普通 Bean 创建模板,但仍可能被后处理器包装。
17.6 SmartFactoryBean
SmartFactoryBean 增加:
isPrototype()isEagerInit()
在 preInstantiateSingletons() 阶段,只有 isEagerInit() 为 true,Spring 才会主动创建产品对象。
十八、Scope、懒加载与跨作用域注入
18.1 singleton 注入 prototype 的陷阱
1 | @Service |
ExportService 只创建一次,所以 ExportTask 也只在创建 ExportService 时解析一次。后续调用不会自动获得新的 prototype。
解决方案:
1 | private final ObjectProvider<ExportTask> taskProvider; |
还可以使用:
@Lookup- scoped proxy
- 注入
ObjectFactory<T> - 重新设计对象边界
18.2 scoped proxy
1 | @RequestScope |
单例 Bean 注入的通常不是当前请求真实对象,而是一个代理:
1 | singleton Bean |
因此代理本身可以安全地作为 singleton 依赖存在。
18.3 @Lazy 的两种含义
标注在 Bean 上
1 | @Lazy |
表示跳过容器启动时的主动预实例化。
标注在注入点上
1 | public OrderService(@Lazy ReportService reportService) { |
表示注入一个延迟解析目标 Bean 的代理,可以打断部分构造器依赖链。
18.4 ObjectProvider 与 @Lazy 的选择
| 需求 | 推荐方式 |
|---|---|
| 调用方不关心延迟机制,只想像普通对象一样使用 | @Lazy 代理 |
| 需要判断 Bean 是否存在 | ObjectProvider.getIfAvailable() |
| 每次获取 prototype | ObjectProvider.getObject() |
| 获取同类型全部 Bean | ObjectProvider.stream() |
| 希望依赖关系显式可读 | 优先 ObjectProvider |
十九、Bean 销毁流程
19.1 哪些 Bean 会自动销毁
容器主要负责 singleton 和由 Scope 管理的 Bean 销毁。
prototype Bean 创建完成后交给调用方,Spring 不会自动执行其完整销毁回调。
19.2 注册销毁适配器
创建阶段结束前:
1 | registerDisposableBeanIfNecessary(beanName, bean, mbd); |
满足以下任一条件时通常需要注册:
- 实现
DisposableBean - 存在
@PreDestroy - 配置 destroy-method
- 推断出
close或shutdown方法 DestructionAwareBeanPostProcessor要处理该 Bean
19.3 容器关闭主线
1 | AbstractApplicationContext.close() |
19.4 销毁顺序
1 | 1. 先销毁依赖当前 Bean 的 dependent Beans |
19.5 为什么依赖方先销毁
如果 OrderService 依赖 DataSource:
1 | 正确:先销毁 OrderService,再销毁 DataSource |
Spring 通过 dependentBeanMap 保证依赖方优先销毁。
19.6 优雅关闭注意事项
1 | server: |
业务 Bean 的销毁方法应:
- 可重复调用或具备幂等性
- 设置超时,避免无限等待
- 正确响应线程中断
- 不再创建新的 Bean
- 不依赖已经销毁的外部资源
二十、异常、回滚与并发创建
20.1 常见异常与阶段
| 异常 | 常见阶段 | 含义 |
|---|---|---|
NoSuchBeanDefinitionException |
依赖解析 | 没有候选 Bean |
NoUniqueBeanDefinitionException |
候选消歧 | 同类型 Bean 无法确定唯一候选 |
UnsatisfiedDependencyException |
构造器或属性注入 | 某个依赖无法解析 |
BeanCurrentlyInCreationException |
循环依赖 | Bean 重复进入创建或原始对象泄露 |
BeanCreationException |
创建总流程 | 构造器、工厂方法、注入或初始化失败 |
BeanIsNotAFactoryException |
FactoryBean 解引用 | 对普通 Bean 使用了 & |
BeanNotOfRequiredTypeException |
类型适配 | 最终对象类型不符合 requiredType |
ScopeNotActiveException |
自定义 Scope | request/session 等作用域当前未激活 |
20.2 异常为什么经常嵌套很深
1 | BeanCreationException: orderController |
排查时从最内层 Caused by 开始,而不是只看最外层 Bean 名称。
20.3 单例创建的同步
Spring Framework 6.1.17 在单例创建路径中使用 singletonObjects 作为同步锁,保证同一个 BeanFactory 中的单例不会被多个线程重复创建。
锁内部可能递归创建其他依赖 Bean。Java 的 synchronized 是可重入的,因此同线程递归进入不会死锁。
20.4 不要在初始化阶段启动失控的异步任务
如果构造器或 @PostConstruct 立即启动新线程,并在新线程中查询尚未完成创建的 Bean,可能造成:
- 读取到未完成初始化的状态
- 与容器单例锁产生等待
- 应用启动顺序不可控
- 关闭时线程无法正确回收
更合适的时机包括:
SmartInitializingSingleton.afterSingletonsInstantiated()ContextRefreshedEvent- Spring Boot
ApplicationReadyEvent SmartLifecycle.start()
二十一、BeanPostProcessor 全阶段地图
21.1 为什么 BeanPostProcessor 是理解创建流程的关键
Spring 的默认创建模板负责搭建骨架,真正的大量扩展能力来自后处理器:
1 | 依赖注入 |
21.2 完整扩展点顺序
| 阶段 | 扩展点 | 典型作用 |
|---|---|---|
| 1 | InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation() |
实例化前直接返回代理或替代对象 |
| 2 | SmartInstantiationAwareBeanPostProcessor.determineCandidateConstructors() |
提供候选构造器 |
| 3 | MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition() |
解析并缓存注入、生命周期元数据 |
| 4 | InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation() |
决定是否继续属性填充 |
| 5 | InstantiationAwareBeanPostProcessor.postProcessProperties() |
执行 @Autowired、@Value、@Resource 注入 |
| 条件阶段 | SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference() |
依赖递归过程中按需创建早期代理,发生在当前 Bean 完成初始化之前 |
| 6 | BeanPostProcessor.postProcessBeforeInitialization() |
ApplicationContextAware、@PostConstruct 等初始化前处理 |
| 7 | BeanPostProcessor.postProcessAfterInitialization() |
AOP 等最终包装 |
| 9 | DestructionAwareBeanPostProcessor.postProcessBeforeDestruction() |
@PreDestroy 等销毁前回调 |
21.3 三个最重要的内置后处理器
AutowiredAnnotationBeanPostProcessor
1 | determineCandidateConstructors() → 选择 @Autowired 构造器 |
CommonAnnotationBeanPostProcessor
1 | postProcessMergedBeanDefinition() → 缓存 @Resource 元数据 |
AbstractAutoProxyCreator
1 | postProcessBeforeInstantiation() → 特殊 TargetSource 提前代理 |
二十二、源码阅读路线
22.1 第一条:从容器启动进入 Bean 创建
1 | AbstractApplicationContext.refresh() |
先理解“为什么创建这个 Bean”,不要一开始就陷入反射细节。
22.2 第二条:单个 Bean 的完整创建
1 | AbstractAutowireCapableBeanFactory.createBean() |
22.3 第三条:构造器参数
1 | createBeanInstance() |
22.4 第四条:字段注入
1 | populateBean() |
22.5 第五条:循环依赖与 AOP
1 | doCreateBean() |
22.6 第六条:销毁
1 | AbstractApplicationContext.close() |
二十三、IDEA 断点调试指南
23.1 主链断点
| 观察目标 | 类 | 方法 |
|---|---|---|
| 批量创建单例 | DefaultListableBeanFactory |
preInstantiateSingletons() |
| 获取 Bean 主入口 | AbstractBeanFactory |
doGetBean() |
| 单例创建锁 | DefaultSingletonBeanRegistry |
getSingleton(String, ObjectFactory) |
| 创建前短路 | AbstractAutowireCapableBeanFactory |
resolveBeforeInstantiation() |
| Bean 核心模板 | AbstractAutowireCapableBeanFactory |
doCreateBean() |
| 选择实例化方式 | AbstractAutowireCapableBeanFactory |
createBeanInstance() |
| 构造器解析 | ConstructorResolver |
autowireConstructor() |
| 属性填充 | AbstractAutowireCapableBeanFactory |
populateBean() |
| 依赖解析 | DefaultListableBeanFactory |
doResolveDependency() |
| 初始化 | AbstractAutowireCapableBeanFactory |
initializeBean() |
| 早期引用 | DefaultSingletonBeanRegistry |
getSingleton(String, boolean) |
| AOP 代理 | AbstractAutoProxyCreator |
wrapIfNecessary() |
| 销毁 | DisposableBeanAdapter |
destroy() |
23.2 重点观察变量
1 | beanName |
23.3 条件断点建议
如果容器 Bean 很多,在以下断点增加条件:
1 | beanName.equals("orderService") |
缓存断点:
1 | beanName.equals("a") || beanName.equals("b") |
依赖解析断点:
1 | descriptor.getDependencyType().getName().contains("PaymentService") |
23.4 调试时不要使用 toString()
IDEA 自动计算变量的 toString() 可能触发:
- 懒加载
- Hibernate 查询
- 代理方法调用
- 新的 Bean 获取
- 集合遍历
复杂源码调试时关闭自动 toString() 展示,优先观察对象类型、字段和 identity hash。
二十四、八个必做实验
24.1 实验一:构造器与字段注入时机
目标:证明字段注入发生在构造器之后。
1 | @Service |
观察断点:
1 | createBeanInstance() |
24.2 实验二:初始化顺序
让 Bean 同时实现:
BeanNameAwareApplicationContextAwareInitializingBean@PostConstruct- 自定义 init-method
- 自定义 BeanPostProcessor
记录输出顺序,并与第十四章对照。
24.3 实验三:多候选依赖
依次测试:
- 两个实现,无任何标记。
- 增加
@Primary。 - 使用
@Qualifier。 - 使用字段名匹配 BeanName。
- 增加
@Priority。
在 determineAutowireCandidate() 观察最终选择。
24.4 实验四:FactoryBean
1 | @Component("carFactory") |
分别观察:
1 | context.getBean("carFactory"); |
并查看 singletonObjects 与 factoryBeanObjectCache。
24.5 实验五:singleton 注入 prototype
连续调用 singleton Bean 的业务方法,观察 prototype 对象是否变化;然后改用 ObjectProvider 再比较。
24.6 实验六:普通循环依赖
临时开启:
1 | spring.main.allow-circular-references: true |
构造 A、B 字段循环,在三个缓存写入和迁移的位置打断点。
实验后必须恢复默认配置。
24.7 实验七:循环依赖与 AOP
为 A 增加 @Transactional,观察:
getEarlyBeanReference()是否返回代理- B 中持有的 A 类型
- 一级缓存中 A 的类型
- 两个对象是否为同一引用
24.8 实验八:创建失败清理
1 | @PostConstruct |
观察:
BeanCreationException的嵌套原因singletonsCurrentlyInCreation是否清理- 三级缓存记录是否清理
- 依赖 Bean 是否受到影响
二十五、常见错误认知
25.1 “new 出对象就完成 Bean 创建了”
错误。构造器只完成实例化,后面还有属性填充、初始化、代理和销毁注册。
25.2 “@Autowired 在构造器之前执行”
字段和方法注入发生在实例化之后。构造器参数注入属于实例化过程的一部分。
25.3 “@PostConstruct 属于 invokeInitMethods()”
错误。它由 InitDestroyAnnotationBeanPostProcessor 在初始化前阶段调用。
25.4 “AOP 代理一定在初始化后创建”
普通情况如此,但循环依赖时可能通过 getEarlyBeanReference() 提前创建代理。
25.5 “三级缓存解决所有循环依赖”
错误。构造器循环、prototype 循环以及某些晚期代理循环无法解决;Boot 3.3.9 默认还禁止循环依赖。
25.6 “FactoryBean 自己就是产品对象”
错误。FactoryBean 是工厂 Bean,默认 BeanName 返回产品,&BeanName 才返回工厂。
25.7 “@Lazy Bean 一定到第一次业务调用才创建”
错误。被非懒 Bean 直接依赖时,依赖解析仍可能提前创建它。
25.8 “prototype Bean 会自动执行 @PreDestroy”
错误。Spring 通常不负责 prototype 的完整销毁,需要调用方自行释放资源。
二十六、面试题与答题框架
26.1 Spring Bean 的完整创建流程是什么
答题框架:
getBean()进入doGetBean(),先查缓存、处理父容器、合并 BeanDefinition 和 Scope。- singleton 通过
getSingleton(beanName, ObjectFactory)保证只创建一次。 createBean()允许 InstantiationAwareBeanPostProcessor 在实例化前短路。doCreateBean()依次执行实例化、MergedBeanDefinition 后处理、早期暴露、属性填充、初始化、早期引用检查和销毁注册。- 初始化后对象可能被 AOP 包装,最终对象进入一级缓存。
26.2 实例化和初始化有什么区别
实例化是通过构造器、工厂方法或 Supplier 得到原始对象;初始化发生在属性注入之后,包括 Aware、@PostConstruct、InitializingBean、init-method 和 BeanPostProcessor 包装。
26.3 为什么构造器循环依赖无法解决
三级缓存是在原始对象实例化之后放入的,而构造器循环在实例化完成前就需要对方对象,没有可提前暴露的引用。
26.4 为什么需要三级缓存
三级缓存保存 ObjectFactory,在真正需要早期引用时调用 getEarlyBeanReference(),从而按需暴露原始对象或早期代理,保证循环依赖双方与容器最终对象一致。
26.5 @Autowired 在哪个阶段执行
构造器候选由 determineCandidateConstructors() 参与实例化;字段和方法注入由 postProcessProperties() 在 populateBean() 阶段执行。
26.6 @PostConstruct 在哪个阶段执行
在 BeanPostProcessor 初始化前阶段,由 InitDestroyAnnotationBeanPostProcessor 调用,早于 InitializingBean.afterPropertiesSet() 和自定义 init-method。
26.7 FactoryBean 和 BeanFactory 有什么区别
BeanFactory 是 IOC 容器接口;FactoryBean 是容器中的特殊 Bean,用于生产另一个对象。
26.8 BeanPostProcessor 为什么可能导致 Bean 提前创建
BeanPostProcessor 本身需要在普通 Bean 前实例化和注册。如果其构造器或创建方法依赖普通业务 Bean,就会导致业务 Bean 在后处理器链尚未完整时提前创建。
二十七、生产实践建议
27.1 优先构造器注入
优点:
- 依赖显式
- 可以使用
final - 创建完成即处于有效状态
- 易于单元测试
- 更容易暴露循环依赖设计问题
27.2 不要在构造器中执行重型业务
避免:
- 数据库查询
- 网络请求
- 启动线程
- 发布依赖完整容器的事件
- 调用可能依赖 AOP 的方法
构造器只负责建立对象基本不变量。
27.3 谨慎使用 @PostConstruct
适合:
- 校验配置
- 构造轻量内存结构
- 初始化无需代理的本地状态
不适合:
- 长时间阻塞操作
- 依赖事务或异步代理
- 调用不稳定外部服务
- 创建无法关闭的线程
27.4 避免业务代码主动 getBean()
主动查询容器会:
- 隐藏依赖
- 增加测试难度
- 容易提前创建 Bean
- 让生命周期难以推断
真正需要动态获取时优先使用 ObjectProvider。
27.5 自定义 BeanPostProcessor 必须轻量
- 不要在构造器中依赖业务 Bean
- 不要在每个 Bean 上重复做昂贵反射扫描
- 缓存解析后的元数据
- 跳过基础设施 Bean 和不相关类型
- 保持幂等,避免重复包装
27.6 不依赖循环引用
Spring Boot 默认禁止循环依赖是在推动更清晰的架构。不要为了让旧设计启动而直接开启全局配置。
27.7 控制启动阶段外部依赖
如果 Bean 初始化必须访问外部系统:
- 设置连接和读取超时
- 明确失败策略
- 考虑延迟初始化
- 提供健康检查
- 避免让非核心能力阻断整个应用启动
二十八、七天学习路线
第一天:只看主线
1 | preInstantiateSingletons() |
目标:能不看资料画出完整主链。
第二天:实例化
学习:
- Supplier
- FactoryMethod
- 构造器选择
- ConstructorResolver
- BeanWrapper
第三天:依赖注入
学习:
- populateBean()
- AutowiredAnnotationBeanPostProcessor
- DependencyDescriptor
- doResolveDependency()
- 多候选消歧
第四天:生命周期与代理
学习:
- Aware
@PostConstruct- InitializingBean
- BeanPostProcessor
- AbstractAutoProxyCreator
第五天:三级缓存
手工推演 A、B 循环依赖,观察三个缓存每一步的变化。
第六天:FactoryBean、Scope 与销毁
完成 FactoryBean、prototype、ObjectProvider、scoped proxy 和销毁实验。
第七天:源码复述与面试
要求自己在 15 分钟内讲清:
- Bean 从 BeanDefinition 到最终对象。
@Autowired的完整链路。- AOP 代理生成时机。
- 三级缓存和循环依赖边界。
- FactoryBean 与销毁流程。
二十九、最终总结
Bean 创建不是一个方法,而是一套可扩展模板:
1 | BeanDefinition |
一句话记忆:
Spring Bean 创建的本质,是以 BeanDefinition 为配方,以 BeanPostProcessor 为扩展链,在 Scope 和单例缓存约束下完成实例化、依赖注入、初始化、代理增强和销毁管理。
真正掌握本文的标准不是背方法名,而是能够回答:
- 当前 Bean 为什么现在创建
- 当前拿到的是原始对象、早期引用还是最终代理
- 当前依赖为何选择这个候选 Bean
- 当前回调发生在实例化、属性填充还是初始化阶段
- 创建失败后哪些缓存和依赖关系需要清理
- 容器关闭时为什么按这个顺序销毁
关联阅读:
01-01-Spring-IOC.md01-02-Spring-IOC-ConfigurationClassPostProcessor.md02-01-Spring-AOP.md
