Spring 创建 Bean 的关键流程

一、什么是 Bean 生命周期?

简单来说,Bean 生命周期就是:

一个 Bean 从创建 → 初始化 → 使用 → 销毁的完整过程

换句话说:

  • 对象什么时候创建?
  • 依赖什么时候注入?
  • 初始化逻辑什么时候执行?
  • 什么时候被销毁?

全部由 Spring 控制,而不是开发者手动管理。

二、Bean 生命周期整体流程

一个完整的 Bean 生命周期可以抽象为:

复制代码
实例化 → 属性填充 → 初始化 → 使用 → 销毁

但如果展开来看,其实远比这复杂:

1. 实例化 Bean

Spring 通过反射创建 Bean 对象(调用构造方法),此时只是"空对象",还没有属性值(Bean 中的成员变量(字段)所对应的值)。

关键点:只执行构造方法,依赖还没注入

2. 属性填充(依赖注入)

Spring 根据配置(XML / 注解如 @Autowired)为 Bean 注入依赖属性。

关键点:完成对象之间的依赖关系绑定

可能会存在循环依赖处理,下来会有一篇专门讲如何解决

3. Aware 接口回调

如果 Bean 实现了 BeanNameAwareBeanFactoryAwareApplicationContextAware 等接口, Spring 会回调这些方法,把容器相关信息注入进去。

关键点:让 Bean "感知" Spring 容器(拿到 BeanName、容器对象等)

4. BeanPostProcessor 前置处理

所有注册的 BeanPostProcessor 会在初始化前执行 postProcessBeforeInitialization() 方法,对 Bean 进行增强或修改。

关键点:可以对 Bean 做"初始化前加工"(例如修改属性)

5. 初始化(InitializingBean / init-method)

执行 Bean 的初始化逻辑,比如:

  • afterPropertiesSet()(实现 InitializingBean)
  • 自定义 init-method

关键点:开发者定义的"初始化完成后要做的事"

6. BeanPostProcessor 后置处理(AOP在这里)

执行 postProcessAfterInitialization() 方法,这一步常用于生成代理对象(比如 AOP)。

关键点:

  • AOP 动态代理在这里产生
  • 返回的可能已经不是原始 Bean,而是代理对象

7. Bean 就绪可用

Bean 完成所有处理,进入容器缓存(单例池),可以被应用程序正常使用。

关键点:

  • 从这里开始,你 getBean() 拿到的就是最终对象(可能是代理对象)

8. 容器关闭 → 销毁

当容器关闭时,执行 Bean 的销毁方法:

  • destroy()(实现 DisposableBean)
  • 自定义 destroy-method

关键点:释放资源(连接、线程、文件等)

三、核心流程详解

1. 实例化 Bean

Spring 在创建 Bean 时,首先会通过反射实例化对象,其核心逻辑位于 createBeanInstance 方法中:

复制代码
Object bean = beanClass.newInstance();

更底层:

复制代码
Constructor<?> constructor = clazz.getDeclaredConstructor();
bean = constructor.newInstance();

此时:

  • 只是一个"空对象"
  • 属性还没有注入
  • 依赖关系还没建立

这一阶段的本质,仅仅是在 JVM 堆中创建了一个对象实例。此时的 Bean 还没有任何依赖关系,属性值也全部为默认值,可以理解为一个"空壳对象"。Spring 并不会简单停留在这里,而是在实例化之后立即执行一个非常关键的操作------提前暴露 Bean 引用

复制代码
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

这一步的作用是将当前 Bean 的"早期引用"放入三级缓存中,从而为后续可能发生的循环依赖提供解决基础

2. 属性填充(依赖注入)

在实例化完成之后,Spring 会进入依赖注入阶段:

复制代码
populateBean(beanName, mbd, instanceWrapper);

在这个过程中,Spring 会解析 Bean 中的依赖(如 @Autowired@Resource),并通过 resolveDependency 方法递归查找或创建依赖对象,最终通过反射完成注入:

复制代码
Object dependency = resolveDependency(...); 
field.set(bean, dependency);

关键点:

  • 这里会触发 依赖查找
  • 循环依赖问题正是发生在这里!我们可以发现, 循环依赖产生的根本原因在于:Bean 之间的依赖关系是在初始化完成之前建立的。

3. Aware 接口回调(容器能力注入)

当依赖注入完成后,Spring 会执行一系列 Aware 接口回调,其核心逻辑位于:

复制代码
invokeAwareMethods(beanName, bean);

Spring 会判断 Bean 是否实现了 BeanNameAwareBeanFactoryAwareApplicationContextAware 等接口,并调用对应方法,例如:

复制代码
((BeanNameAware) bean).setBeanName(beanName);

这一阶段的设计目的,是让 Bean 可以"感知"容器的存在,从而具备获取上下文信息的能力。

一句话总结:开发者可以主动从 Spring 容器里"拿东西",而不是等容器注入

例如,开发者可以通过 ApplicationContext 手动获取其他 Bean

复制代码
ApplicationContext context = ...;

UserService userService = context.getBean("userService");

从设计角度来看,这是一种典型的"控制反转中的反向注入"。

4. 初始化前处理(BeanPostProcessor 前置增强)

在正式初始化之前,Spring 会执行 BeanPostProcessor 的前置方法,其源码如下:

复制代码
applyBeanPostProcessorsBeforeInitialization(bean, beanName);

底层实现是遍历所有 BeanPostProcessor:

复制代码
for (BeanPostProcessor processor : processors) {
    bean = processor.postProcessBeforeInitialization(bean, beanName);
}

BeanPostProcessor 的前置处理方法是在初始化方法执行之前调用的,主要用于对 Bean 进行统一的增强、修改或预处理,属于 Spring 提供的扩展机制;而 @PostConstruct 等初始化方法则是开发者定义的,用于完成 Bean 自身的初始化逻辑。两者的主要区别在于职责不同,一个偏框架扩展,一个偏业务初始化。

5. 初始化阶段(Bean 完整构建)

在前置处理完成后,Spring 会进入真正的初始化阶段,其核心方法为:

复制代码
invokeInitMethods(beanName, bean, mbd);

Spring 支持多种初始化方式,例如实现 InitializingBean 接口:

复制代码
afterPropertiesSet();

或者通过配置 init-method

复制代码
<bean init-method="init"/>

BeanPostProcessor 前置处理主要用于对 Bean 进行统一的增强或修改,属于框架级扩展机制;而初始化阶段(如 @PostConstruct 或 afterPropertiesSet)则由开发者定义,用于完成 Bean 的业务初始化逻辑,使其具备实际可用性。两者的区别在于,前者侧重于"加工 Bean",后者侧重于"使用 Bean"。

这一阶段的本质,是对 Bean 做最后的"加工处理",例如参数校验、资源加载、连接初始化等。可以认为,Bean 在这里才真正具备"业务可用性"。

6. 初始化后处理(AOP 代理生成关键阶段🔥)

初始化完成后,Spring 会再次执行 BeanPostProcessor,但这一次是后置处理:

复制代码
applyBeanPostProcessorsAfterInitialization(bean, beanName);

其核心逻辑通常表现为:

复制代码
return wrapIfNecessary(bean);

这一阶段是 Spring AOP 实现的关键入口。如果 Bean 满足切面条件(如被 @Transactional@Aspect 等增强),Spring 会在这里为其创建代理对象(JDK 动态代理或 CGLIB)。因此,最终放入容器中的对象,很可能已经不是原始对象,而是一个代理对象。这一点在理解循环依赖时尤为重要,因为"提前暴露"的对象必须与最终对象保持一致,否则会导致代理失效问题。

总结:在 Spring 中,如果 Bean 被 AOP 增强,那么容器中最终存储的并不是原始对象,而是其代理对象,BeanName 不发生变化。开发者通过依赖注入或 getBean 获取到的也是该代理对象。原始对象只在 Bean 创建过程中短暂存在,不会被放入一级缓存,也不会被直接使用。

7. Bean 就绪(进入容器,可被使用)

当上述所有步骤完成后,Bean 会被放入单例缓存中(singletonObjects),正式成为容器中的一个可用组件。此时 Bean 已经完成了依赖注入、初始化以及可能的 AOP 增强,可以被其他 Bean 或业务代码正常获取:

复制代码
context.getBean(A.class);

从生命周期角度来看,这一阶段是 Bean 存在时间最长、最稳定的阶段。

8. Bean 销毁(生命周期终点)

当 Spring 容器关闭时(例如调用 context.close()),会触发 Bean 的销毁流程。其核心逻辑包括:

复制代码
DisposableBean.destroy();

以及:

复制代码
@PreDestroy

或:

复制代码
<bean destroy-method="destroy"/>

这一阶段主要用于释放资源,例如关闭数据库连接、线程池、网络连接等。从设计角度来看,这是对资源管理的一种"兜底机制",确保系统优雅关闭。

四、总结

从整体来看,Spring 中的 Bean 生命周期本质上是一个"逐步加工对象"的过程。

从源码执行顺序来看,这一过程可以概括为:实例化 → 提前暴露 → 依赖注入 → 初始化 → AOP增强 → 使用 → 销毁。其中一个非常关键的细节在于:依赖注入发生在初始化之前,而 Bean 在实例化之后就已经被提前暴露。这意味着,一个 Bean 在尚未完全构建完成时,就有可能被其他 Bean 所引用。

正是这一设计,引出了一个经典且不可避免的问题:如果两个 Bean 之间存在相互依赖关系(如 A 依赖 B,B 又依赖 A),那么在依赖注入阶段就会出现"对象尚未完成初始化却被再次请求"的情况。这种现象就是所谓的循环依赖问题。

那么,Spring 是如何在允许这种"半成品对象被引用"的前提下,仍然保证系统正常运行的?又是如何避免无限递归创建的?这正是下一步需要深入分析的核心------基于三级缓存的循环依赖解决机制

相关推荐
SuperEugene2 小时前
Vue3 性能优化规范:日常必做优化(不玄学、可落地)|可维护性与兜底规范篇
开发语言·前端·javascript·vue.js·性能优化·前端框架
Frank_refuel2 小时前
QT->信号与槽详解上(概述、使用、自定义、连接方式、其他说明)
开发语言·qt
深耕AI2 小时前
【VS Code 中 Python 虚拟环境降级完整指南(含 uv 工具实战)】
开发语言·python·uv
TlYf NTLE2 小时前
Spring Boot3.3.X整合Mybatis-Plus
spring boot·后端·mybatis
若年封尘2 小时前
告别手写 API 类型:用 openapi-fetch 打造类型安全的前端接口层
前端·安全·openapi-fetch
rOuN STAT2 小时前
MySQL:基础操作(增删查改)
java
cypking2 小时前
二次封装ElementUI日期范围组件:打造带限制规则的Vue2 v-model响应式通用组件
前端·javascript·elementui
→长歌2 小时前
2026Java面试30题精解
java·python·面试
A923A2 小时前
【小兔鲜电商前台 | 项目笔记】第二天
前端·vue.js·笔记·项目·小兔鲜