← 返回文章

Bean 创建过程

发布于 2026/10/21

1. 完整回答

Spring Bean 的创建过程,就是 IoC 容器根据 BeanDefinition 创建对象,完成依赖注入、初始化和必要的代理包装,最终将 Bean 交给业务使用的过程。通过这套机制,Spring 可以统一管理对象的创建、依赖关系和生命周期。

首先,Spring 在容器启动时,会通过组件扫描、配置类、@Bean 等方式注册 BeanDefinition,里面保存了 Bean 的类型、作用域、依赖关系等信息。默认情况下,非懒加载的单例 Bean 会在容器启动阶段创建。

真正创建 Bean 时,Spring 会先检查单例缓存。如果已经存在,就直接返回;如果不存在,就根据 BeanDefinition 选择构造方法或工厂方法进行实例化。

实例化完成后,进入属性填充阶段,处理 @Autowired、@Resource 等依赖注入。接着执行 Aware 接口回调,让 Bean 感知容器提供的信息,然后通过 BeanPostProcessor 执行初始化前处理,调用 @PostConstruct、afterPropertiesSet 等初始化方法。

初始化完成后,还会执行 BeanPostProcessor 的初始化后处理。如果 Bean 需要 AOP 增强,通常会在这个阶段创建代理对象。最后将完整的单例 Bean 放入一级缓存,后续获取时可以直接复用。

另外,Spring 还提供了三级缓存机制,在满足条件时可以提前暴露尚未完成初始化的单例 Bean 引用,解决部分循环依赖问题。

这套流程在 Service、Repository、事务代理等组件中都会涉及。理解创建过程后,就能进一步分析依赖注入失败、循环依赖以及 AOP 代理相关的问题。

2. 核心原理与关键概念

2.1 核心原理

整体流程:

BeanDefinition 注册 → 获取 Bean → 实例化 → 属性填充 → Aware 回调 → 初始化前处理 → 初始化方法 → 初始化后处理 → 单例缓存

步骤 A:解析并注册 BeanDefinition

Spring 通过组件扫描、配置类、@Bean 等方式收集 Bean 定义。

BeanDefinition 相当于 Bean 的创建说明,记录类型、作用域、构造参数、初始化方法等元数据。

在创建普通 Bean 之前,容器还会执行 BeanFactoryPostProcessor,允许对 BeanDefinition 进行调整。

步骤 B:获取 Bean 并判断是否需要创建

调用 getBean() 时,Spring 会先尝试获取已有实例。

  • 如果单例缓存中已有 Bean,直接返回。

  • 如果没有实例,则根据 BeanDefinition 进入创建流程。

  • 非懒加载的普通单例 Bean,通常会在容器刷新阶段提前创建。

步骤 C:实例化 Bean

Spring 根据 BeanDefinition 选择无参构造、有参构造或工厂方法创建对象。

如果使用构造器注入,需要先解析构造参数所依赖的 Bean,再完成当前对象的实例化。

此时对象通常还没有完成字段注入和初始化。

步骤 D:提前暴露单例引用

对于允许循环依赖、且正在创建的单例 Bean,Spring 会在实例化之后、属性填充之前,将一个用于获取早期引用的 ObjectFactory 放入三级缓存。

后续如果其他 Bean 需要引用当前 Bean,就有机会通过这个工厂获取早期引用,避免重复实例化。

步骤 E:属性填充

Spring 解析并注入 Bean 所依赖的对象和属性。

例如通过 @Autowired 注入字段或 Setter 方法依赖。这个过程中,如果依赖的 Bean 尚未创建,Spring 会继续触发对应 Bean 的创建。

步骤 F:初始化 Bean

主要执行顺序:

  1. 执行相关 Aware 回调,例如 BeanNameAware、BeanFactoryAware。

  2. 执行 BeanPostProcessor 初始化前处理,其中包括通过后处理器调用 @PostConstruct。

  3. 执行 InitializingBean.afterPropertiesSet()。

  4. 执行自定义初始化方法,例如 @Bean(initMethod = "init")。

  5. 执行 BeanPostProcessor 初始化后处理,按需完成 AOP 代理包装。

ApplicationContextAware 等部分 Aware 回调也通过初始化前处理器完成。

步骤 G:完成创建并缓存

对于普通单例 Bean,Spring 会将最终对外暴露的对象放入一级缓存,并清理对应的早期引用缓存。

对于原型 Bean,每次请求通常都会创建新实例,不进入单例缓存。

2.2 关键概念

概念 作用
BeanDefinition 描述 Bean 的创建方式、依赖、作用域和生命周期配置
BeanFactory 提供 Bean 创建、依赖解析和获取能力的基础容器接口
ApplicationContext 在 BeanFactory 基础上提供事件、资源访问等应用级能力
BeanPostProcessor 在 Bean 初始化前后执行扩展逻辑,也可参与代理包装
Aware 接口 让 Bean 获取自身名称、容器等相关信息
三级缓存 管理单例的完整实例、早期引用和早期引用工厂
AOP 代理 为符合条件的 Bean 提供事务、切面等方法增强能力

3. 高频面试追问

3.1 Spring 从 getBean 到创建 Bean,经过哪些核心方法?

核心流程是 doGetBean() → createBean() → doCreateBean(),分别负责获取与创建判断、创建前处理,以及实际创建过程。

getBean() 最终会进入 doGetBean(),先查找单例缓存。如果没有,就解析 BeanDefinition、处理依赖关系,再根据作用域决定如何创建 Bean。

createBean() 会先执行实例化前处理,允许某些后处理器提前返回代理对象。

如果没有提前返回,就进入 doCreateBean(),依次调用:

createBeanInstance() → populateBean() → initializeBean()

分别完成实例化、属性填充和初始化。普通单例 Bean 创建成功后,再注册到单例缓存中。

3.2 Bean 的实例化和初始化有什么区别?

实例化负责创建对象,初始化负责在依赖配置完成后执行生命周期回调。

实例化阶段主要通过构造方法或工厂方法得到对象。例如执行 new UserService(),此时对象已经存在,但字段依赖可能还没有注入。

属性填充完成后,Spring 才进入初始化阶段,执行 Aware 回调、BeanPostProcessor 和相关初始化方法。

如果同时配置了三种不同的初始化回调,执行顺序为:

@PostConstruct → afterPropertiesSet() → 自定义 initMethod

例如,一个 Service 的构造方法负责创建对象,依赖注入负责设置 Repository,而 @PostConstruct 可以在依赖完成后进行必要的状态检查或资源准备。

3.3 构造器注入和字段注入在创建过程中有什么区别?

核心区别是依赖注入的时机。

构造器注入发生在实例化过程中。Spring 必须先解析构造参数对应的依赖,才能调用构造方法创建当前 Bean。

字段注入和 Setter 注入发生在实例化之后的属性填充阶段,主要由相关 BeanPostProcessor 处理,例如 AutowiredAnnotationBeanPostProcessor。

构造器注入更适合必需依赖,能够保证对象创建时就具备这些依赖,也方便使用 final 字段。

字段注入虽然简洁,但对象在构造完成时可能还没有获得所需依赖,独立测试和显式管理依赖关系也相对困难。

3.4 BeanFactoryPostProcessor 和 BeanPostProcessor 有什么区别?

BeanFactoryPostProcessor 主要处理 BeanDefinition,BeanPostProcessor 主要处理 Bean 实例。

BeanFactoryPostProcessor 在普通 Bean 实例化前执行,可以修改 BeanDefinition,例如调整配置属性或者注册额外的 BeanDefinition。

BeanPostProcessor 则参与 Bean 的创建过程,能够在初始化前后处理对象,甚至返回包装后的代理对象。

例如,ConfigurationClassPostProcessor 负责解析配置类及相关 Bean 定义,属于 BeanFactoryPostProcessor 体系。

而 Spring AOP 使用的自动代理创建器属于 BeanPostProcessor 体系,可以在 Bean 创建过程中生成代理。

两者的区别主要是处理对象不同,执行阶段也不同。

3.5 Spring 的三级缓存是如何解决循环依赖的?

核心机制是提前暴露已经实例化、但尚未完成初始化的单例 Bean 引用。

三级缓存分别保存不同阶段的对象:

缓存 存储内容 主要作用
一级 singletonObjects 完整单例 Bean 对外提供最终的单例对象
二级 earlySingletonObjects 提前暴露的 Bean 引用 复用早期引用
三级 singletonFactories ObjectFactory 按需生成早期引用,必要时创建代理

假设 A 依赖 B,B 又依赖 A,且都采用字段注入:

  1. Spring 实例化 A,将 A 的早期引用工厂放入三级缓存。

  2. 为 A 注入 B,触发 B 的创建。

  3. B 实例化后需要注入 A。

  4. Spring 发现 A 正在创建,从三级缓存取得 A 的早期引用,并放入二级缓存。

  5. B 完成创建,再注入 A。

  6. A 继续完成初始化,最终进入一级缓存。

这样就能在 A 尚未完成创建时,让 B 先获得 A 的引用,打破相互等待。

继续追问:提前暴露的 Bean 会不会一直处于未初始化状态?

正常情况下不会。提前暴露的是同一个目标对象的引用,或者与其关联的早期代理。随着后续属性填充和初始化完成,目标对象也会逐步完成配置。

不过,如果在初始化完成前就调用它的业务方法,仍然可能访问到尚未准备好的状态,因此循环依赖即使能够解决,也存在设计风险。

3.6 为什么需要三级缓存?两级缓存不够吗?

三级缓存的关键价值,是支持早期引用的按需创建,尤其是 AOP 代理场景。

如果只考虑普通对象的属性注入循环依赖,提前保存原始对象引用,通过两级缓存也可以实现类似效果。

但如果 Bean 需要 AOP 增强,其他 Bean 最终应该持有正确的代理引用。

三级缓存保存 ObjectFactory,在真正需要提前引用时,通过 getEarlyBeanReference() 获取原始对象或提前创建的代理。

生成的早期引用会放进二级缓存,避免重复生成不同的代理对象。

因此:

  • 一级缓存管理创建完成的单例。

  • 二级缓存复用已经生成的早期引用。

  • 三级缓存提供按需获取早期引用的能力。

这样能够更好地协调循环依赖和 AOP 代理的一致性。

3.7 Spring 能解决所有循环依赖吗?

不能。三级缓存主要支持特定条件下的单例 Bean 属性注入循环依赖。

构造器循环依赖通常无法通过三级缓存解决。因为 A 创建时必须先获得 B,而 B 创建时又必须获得 A,此时 A 连实例化都没有完成,也就没有可以提前暴露的对象。

Prototype 作用域的循环依赖也无法依靠单例三级缓存机制解决。

另外,Spring Boot 默认禁止循环引用。即使是理论上可以提前暴露引用的单例属性注入循环依赖,也可能直接导致启动失败。

在项目中通常优先调整依赖关系,例如抽取公共服务、拆分职责。如果确实需要延迟获取依赖,可以结合 @Lazy 或 ObjectProvider,但要保证设计本身合理。

3.8 AOP 代理是在 Bean 创建的哪个阶段生成的?

通常在 Bean 初始化后处理阶段生成,由 BeanPostProcessor 负责。

Spring AOP 的自动代理创建器会判断当前 Bean 是否需要增强。如果符合切面条件,就创建代理对象,并将代理返回给容器。

后续其他 Bean 获取该 Bean 时,通常拿到的是代理对象。

如果存在循环依赖,Spring 也可能通过 getEarlyBeanReference() 提前创建代理,保证其他 Bean 注入的引用与最终暴露的引用保持一致。

例如 @Transactional,通常需要通过代理拦截方法调用才能开启事务。由于目标对象内部通过 this 调用其他方法时绕过了代理,所以同类方法自调用可能导致事务增强不生效。

继续追问:为什么在 @PostConstruct 中调用 @Transactional 方法通常不会开启事务?

因为 @PostConstruct 在初始化前处理阶段执行,此时常规 AOP 代理尚未完成最终包装。

而且初始化方法内部直接调用当前对象的方法,也没有经过事务代理。

因此,依赖事务执行的数据库初始化逻辑,更适合放到能够通过代理调用的独立服务中,并在合适的应用启动阶段触发。

3.9 Singleton、Prototype 和 @Lazy 对 Bean 创建有什么影响?

作用域决定实例的复用方式,懒加载决定是否延迟创建。

Singleton 是默认作用域,同一个容器中,同一 Bean 定义通常对应一个共享实例。非懒加载的单例 Bean 一般在容器刷新时创建。

Prototype 每次被请求时通常创建一个新对象,不使用单例缓存。Spring 会完成它的初始化,但默认不会在容器关闭时自动执行其销毁回调。

@Lazy 可以推迟单例 Bean 的预实例化。不过,如果懒加载 Bean 被非懒加载单例直接依赖,仍然可能在启动时被创建。

继续追问:单例 Bean 注入 Prototype Bean,每次调用都会得到新对象吗?

不会自动得到新对象。普通依赖注入只在单例创建时执行一次,因此后续一直使用当时注入的 Prototype 实例。

如果希望每次使用时获取新对象,可以注入 ObjectProvider,在业务调用时通过 getObject() 获取。

3.10 项目中 Bean 创建失败,通常怎么排查?

先根据异常类型确定失败阶段,再沿着 Bean 的依赖链定位根因。

如果出现 NoSuchBeanDefinitionException,通常先检查组件扫描范围、条件注册是否生效,以及需要的 Bean 是否已经注册。

如果出现 NoUniqueBeanDefinitionException,说明依赖解析存在多个无法确定唯一候选者的 Bean,可以使用 @Primary 或 @Qualifier 明确选择。

如果出现 BeanCurrentlyInCreationException,重点检查构造器循环依赖、原型循环依赖或不允许提前暴露引用的情况。

如果出现 BeanCreationException,还需要检查构造方法、属性填充和初始化回调是否抛出了异常。

例如,Service 注入支付接口时存在两个实现类,导致启动失败,就应该先检查候选 Bean,再明确指定需要的实现。

排查时会优先查看异常链中最底层的原因,以及日志中的 Bean 依赖关系,避免只根据最外层异常判断问题。

4. 易混淆概念对比

4.1 BeanDefinition 与 Bean

对比项 BeanDefinition Bean
本质 Bean 的定义元数据 容器管理的对象实例
产生阶段 定义解析与注册阶段 实例化阶段
主要内容 类型、作用域、依赖、生命周期配置 实际对象状态与行为
作用 指导容器创建 Bean 提供具体业务能力

一个 BeanDefinition 可以根据作用域产生不同数量的对象实例。

4.2 实例化与初始化

对比项 实例化 初始化
发生时机 属性填充之前 属性填充之后
核心操作 构造方法、工厂方法 生命周期回调
主要作用 创建对象 完成初始化逻辑
典型方式 构造器调用 @PostConstruct、afterPropertiesSet()

4.3 BeanFactoryPostProcessor 与 BeanPostProcessor

对比项 BeanFactoryPostProcessor BeanPostProcessor
处理对象 BeanDefinition Bean 实例
执行时机 普通 Bean 实例化前 Bean 创建过程中
典型用途 修改 Bean 定义 初始化扩展、代理包装
代表实现 ConfigurationClassPostProcessor 自动代理创建器

4.4 构造器注入与字段、Setter 注入

对比项 构造器注入 字段、Setter 注入
注入时机 实例化过程中 实例化之后
依赖可用性 创建完成时已获得构造参数依赖 属性填充后才完整
循环依赖 普通相互构造依赖无法提前暴露 满足条件时可通过三级缓存解决
适用场景 必需依赖 可选依赖或特定框架场景

4.5 Singleton 与 Prototype

对比项 Singleton Prototype
默认行为 同一容器、同一 Bean 定义共享实例 每次请求创建新实例
单例缓存 使用 不使用
创建时机 默认启动时预实例化 被请求时创建
初始化回调 支持 支持
销毁回调 容器通常负责管理 使用方负责清理

5. 总结

Spring Bean 的创建过程,核心就是 根据 BeanDefinition 完成实例化、依赖注入、初始化和必要的代理包装。容器先判断是否已有可复用的实例,再选择构造方法或工厂方法创建对象,随后填充属性,执行 Aware 回调和 BeanPostProcessor,完成初始化后将单例对象放入缓存。整个过程中,BeanPostProcessor 提供了生命周期扩展和 AOP 代理能力,三级缓存能够在满足条件时解决部分单例循环依赖。需要注意,构造器循环依赖通常无法通过提前暴露引用解决,而且 Spring Boot 默认禁止循环引用,因此实际项目中仍然应该优先保证依赖关系清晰。