🍳 Spring Bean 生命周期全解析:用“做红烧肉”讲透每一个阶段
作者:赫英年
关键词:Spring Bean 生命周期、依赖注入、BeanPostProcessor、@PostConstruct、AOP 原理
🔥 引言:做一道“红烧肉”,理解一个 Spring Bean
在 Spring 框架中,Bean 的生命周期是核心机制之一。
今天我们用一道“家常红烧肉”来类比整个流程,从买肉到洗碗,完整还原 Spring 容器是如何管理一个 Bean 的。
🧩 整体比喻回顾:做一道“红烧肉” = 创建一个 Spring Bean
生活阶段 | Spring 阶段 | 代码演示 |
---|---|---|
1. 买肉 | 实例化(Instantiation) | new BraisedPork() |
2. 腌肉 | 属性赋值(Dependency Injection) | @Autowired 酱油、料酒 |
3. 切肉前看看 | 初始化前(postProcessBeforeInitialization) | 检查/替换对象 |
4. 正式开火 | 初始化(@PostConstruct / init-method ) | 焯水、炒糖色 |
5. 上锅前再检查 | 初始化后(postProcessAfterInitialization) | 加“代理”(如事务) |
6. 上菜 | 使用 Bean | service.cook() |
7. 洗碗 | 销毁(@PreDestroy / destroy-method ) | 关火、洗锅 |
🧱 1. 阶段一:买肉 —— 实例化(Instantiation)
比喻:去市场买一块五花肉 → 创建对象实例。
// BraisedPork.java
public class BraisedPork {
private String name;
private SoySauce soySauce; // 酱油(依赖)
private CookingOil oil; // 食用油(依赖)
// 无参构造:相当于“买肉”
public BraisedPork() {
System.out.println("🥩 1. 买肉:BraisedPork 实例化了!new 出来了!");
}
// setter 用于注入依赖(腌肉)
public void setName(String name) {
this.name = name;
}
public void setSoySauce(SoySauce soySauce) {
this.soySauce = soySauce;
}
public void setOil(CookingOil oil) {
this.oil = oil;
}
public void cook() {
System.out.println("🍳 正在烹饪:" + name + ",用的酱油是:" + soySauce.getType());
}
}
✅ 实例化就是 new BraisedPork()
,Spring 容器负责调用构造函数。
🧂 2. 阶段二:腌肉 —— 属性赋值(Populate Properties)
比喻:用酱油、料酒腌肉 → 注入依赖。
// SoySauce.java
public class SoySauce {
private String type;
public SoySauce(String type) {
this.type = type;
}
public String getType() {
return type;
}
}
// CookingOil.java
public class CookingOil {
private String brand;
public CookingOil(String brand) {
this.brand = brand;
}
}
// AppConfig.java
import org.springframework.context.annotation.*;
@Configuration
public class AppConfig {
@Bean
public SoySauce soySauce() {
return new SoySauce("老抽");
}
@Bean
public CookingOil cookingOil() {
return new CookingOil("金龙鱼");
}
@Bean
public BraisedPork braisedPork() {
BraisedPork pork = new BraisedPork();
pork.setName("家常红烧肉"); // 这一步就是“腌肉”
return pork;
}
}
✅ Spring 会自动把 soySauce()
和 cookingOil()
注入到 BraisedPork
中(通过 setter 或字段注入)。
🔍 为什么能自动注入 soySauce()
和 cookingOil()
?—— Spring 容器的“自动装配”机制
这段代码:
@Bean
public BraisedPork braisedPork() {
BraisedPork pork = new BraisedPork();
pork.setName("家常红烧肉");
return pork;
}
看起来确实没有注入 SoySauce
和 CookingOil
,但 Spring 会在你返回这个对象之后,自动完成依赖注入!
🔧 关键机制:属性注入发生在 @Bean
方法返回之后
Spring 的流程是这样的:
- 调用你的
braisedPork()
方法,得到一个BraisedPork
实例(此时只有name
被设置)。 - Spring 拿到这个实例后,扫描它的字段或 setter 方法。
- 发现它有
setSoySauce(SoySauce)
和setOil(CookingOil)
方法(或@Autowired
字段)。 - Spring 去容器里找类型匹配的 Bean(
SoySauce
和CookingOil
)。 - 找到后,自动调用
pork.setSoySauce(...)
和pork.setOil(...)
。 - 最终放入容器的
BraisedPork
是已经被注入好依赖的完整对象。
✅ 举个更直白的例子
假设 Spring 内部是这样工作的(简化版):
// 第一步:调用你的 @Bean 方法
BraisedPork pork = appConfig.braisedPork();
// 此时 pork.name = "家常红烧肉",但 soySauce = null, oil = null
// 第二步:Spring 自动注入依赖
SoySauce sauce = appConfig.soySauce(); // 从容器获取
CookingOil oil = appConfig.cookingOil(); // 从容器获取
pork.setSoySauce(sauce); // Spring 自动调用
pork.setOil(oil); // Spring 自动调用
// 第三步:这个 pork 才真正放入 Spring 容器
container.put("braisedPork", pork);
所以,你不需要手动写 setSoySauce()
,Spring 会帮你补上!
🔍 3. 阶段三:切肉前看看 —— 初始化前(postProcessBeforeInitialization
)
比喻:你正要切肉,发现肉太肥,决定换成瘦肉 → 可检查或替换 Bean。
// MyBeanPostProcessor.java
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("🔪 3. 切肉前检查:Bean '" + beanName + "' 即将初始化...");
// 检查是不是红烧肉
if (bean instanceof BraisedPork) {
BraisedPork pork = (BraisedPork) bean;
System.out.println("🔍 发现红烧肉,当前名字:" + pork.getClass().getSimpleName());
// 可以修改它
// 注意:
// 1. 如果 name 是在 @Bean 方法中通过 setName() 手动设置的 → 此时已赋值,可安全修改
// 2. 如果 name 是通过 @Value("${...}") 注入的 → 此时可能还未注入(@Value 处理时机较晚)
// 在 postProcessBeforeInitialization 中读取或修改,可能拿到 null 或默认值
// 改了也白改,因为后续还会被 @Value 覆盖 }
// 甚至可以替换整个对象(高级玩法)
/*
if ("braisedPork".equals(beanName)) {
System.out.println("⚠️ 强行替换了红烧肉!");
return new BraisedPork() {{
setName("被替换的红烧肉");
}};
}
*/
return bean; // 继续使用原对象
}
}
✅ 这个方法在 @PostConstruct
之前调用,你可以:
- 打日志
- 做安全检查
- 返回一个包装对象(如代理)
- 极端情况下替换整个 Bean(不推荐)
🔥 4. 阶段四:正式开火 —— 初始化(Initialization)
比喻:焯水、炒糖色、加香料 → 准备工作。
// 在 BraisedPork.java 中添加:
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.InitializingBean;
public class BraisedPork implements InitializingBean {
// ... 其他代码
@PostConstruct
public void startFire() {
System.out.println("🔥 4.1 @PostConstruct:开火!开始炒糖色...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("🔥 4.2 afterPropertiesSet:加八角、桂皮,调料下锅!");
}
public void customInit() {
System.out.println("🔥 4.3 customInit:大火收汁,准备出锅!");
}
}
// 修改 AppConfig.java 中的 Bean 定义:
@Bean(initMethod = "customInit")
public BraisedPork braisedPork() {
BraisedPork pork = new BraisedPork();
pork.setName("家常红烧肉");
return pork;
}
✅ 执行顺序:
@PostConstruct
afterPropertiesSet()
init-method
👨🍳 5. 阶段五:上锅前再检查 —— 初始化后(postProcessAfterInitialization
)
比喻:菜快好了,你给它加个“防伪标签”或“拍照” → AOP 代理生成。
// 继续在 MyBeanPostProcessor.java 中:
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("👨🍳 5. 上锅前检查:Bean '" + beanName + "' 已初始化完成!");
if (bean instanceof BraisedPork) {
System.out.println("📸 给红烧肉加个‘美食滤镜’(AOP 代理)!");
// 真实场景:Spring AOP 就在这里生成代理对象
// 比如 @Transactional、@Cacheable 都是这么加的
}
return bean; // 实际上可以返回一个代理对象
}
✅ 这是 AOP 的核心阶段!Spring 在这里给你的 Bean “包一层”,比如加事务控制。
🍽️ 6. 阶段六:上菜 —— 使用 Bean
// App.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// 上菜!
BraisedPork pork = ctx.getBean(BraisedPork.class);
System.out.println("🍽️ 6. 上菜啦!");
pork.cook();
// 关闭容器(触发销毁)
((AnnotationConfigApplicationContext) ctx).close();
}
}
🧽 7. 阶段七:洗碗 —— 销毁(Destruction)
比喻:关火、洗锅、收拾厨房 → 释放资源。
// 在 BraisedPork.java 中添加:
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.DisposableBean;
public class BraisedPork implements InitializingBean, DisposableBean {
// ... 其他代码
@PreDestroy
public void turnOffFire() {
System.out.println("🧽 7.1 @PreDestroy:关火!");
}
@Override
public void destroy() throws Exception {
System.out.println("🧽 7.2 DisposableBean.destroy:洗锅!");
}
public void customDestroy() {
System.out.println("🧽 7.3 customDestroy:擦灶台,打扫厨房!");
}
}
// 修改 AppConfig.java
@Bean(initMethod = "customInit", destroyMethod = "customDestroy")
public BraisedPork braisedPork() {
// ...
}
✅ 执行顺序:
@PreDestroy
destroy()
destroy-method
🏁 运行结果(控制台输出)
深色版本🥩 1. 买肉:BraisedPork 实例化了!new 出来了!
🔪 3. 切肉前检查:Bean 'braisedPork' 即将初始化...
🔥 4.1 @PostConstruct:开火!开始炒糖色...
🔥 4.2 afterPropertiesSet:加八角、桂皮,调料下锅!
🔥 4.3 customInit:大火收汁,准备出锅!
👨🍳 5. 上锅前检查:Bean 'braisedPork' 已初始化完成!
📸 给红烧肉加个‘美食滤镜’(AOP 代理)!
🍽️ 6. 上菜啦!
🍳 正在烹饪:家常红烧肉,用的酱油是:老抽
🧽 7.1 @PreDestroy:关火!
🧽 7.2 DisposableBean.destroy:洗锅!
🧽 7.3 customDestroy:擦灶台,打扫厨房!
✅ 总结:代码 + 比喻 对照表
阶段 | 比喻 | 代码关键点 |
---|---|---|
1. 实例化 | 买肉 | new BraisedPork() |
2. 属性赋值 | 腌肉 | @Autowired 、setter |
3. 初始化前 | 切肉前检查 | BeanPostProcessor.postProcessBeforeInitialization |
4. 初始化 | 开火做菜 | @PostConstruct 、afterPropertiesSet 、init-method |
5. 初始化后 | 加滤镜/包装 | BeanPostProcessor.postProcessAfterInitialization (AOP) |
6. 使用 | 上菜 | getBean().cook() |
7. 销毁 | 洗碗 | @PreDestroy 、destroy 、destroy-method |