文章目录
- 一、结论先行
- [二、ApplicationReadyEvent 到底是什么?](#二、ApplicationReadyEvent 到底是什么?)
- [三、Spring Boot 启动生命周期](#三、Spring Boot 启动生命周期)
- 四、你在做什么?为什么这件事对时机敏感?
- [五、为什么不能随便写在 @PostConstruct 里?](#五、为什么不能随便写在 @PostConstruct 里?)
- [六、为什么 ApplicationReadyEvent 是"黄金时间点"?](#六、为什么 ApplicationReadyEvent 是“黄金时间点”?)
- 七、用生活比喻
- 八、几种常见初始化方式对比
- 九、更推荐的工程写法(两种)
- 十、总结
很多时候看到下面这行代码会懵:
java
public class RabbitInitConfigApplicationListener
implements ApplicationListener<ApplicationReadyEvent>
看起来只是"监听一个事件",但它背后体现的是 Spring Boot 生命周期理解 与 RabbitTemplate 初始化时机控制。
ApplicationReadyEvent到底是什么- 为什么不能随便在
@PostConstruct里 setConfirmCallback - RabbitTemplate 的自动配置顺序是怎样的
- 常见坑有哪些
- 推荐的工程写法是什么
一、结论先行
implements ApplicationListener<ApplicationReadyEvent>的意思是:👉 等 Spring Boot 应用完全启动之后,再执行初始化逻辑。
也就是:
👉 等所有 Bean + 自动配置都完成之后,再给 RabbitTemplate 设置回调。
这种写法的核心价值是:避免初始化太早被覆盖 / 无效 / 不稳定。
二、ApplicationReadyEvent 到底是什么?
Spring Boot 启动过程中会发布多个事件,常见的包括:
ApplicationStartingEventApplicationEnvironmentPreparedEventApplicationPreparedEventContextRefreshedEventApplicationStartedEvent- ✅
ApplicationReadyEvent(最晚、最稳)
你可以把它理解成:"应用已经能对外提供服务了"
当 ApplicationReadyEvent 发布时,一般意味着:
- Spring 容器已经刷新完成
- 所有 Bean 都初始化完了
- CommandLineRunner / ApplicationRunner 都已经执行完
- Web 容器(如 Tomcat)已经启动并监听端口
- 应用已经 ready(可以接请求)
👉 所以它是一个"非常稳妥的初始化时机"。
三、Spring Boot 启动生命周期
为了准确理解"为什么必须等到 ready",我们把启动流程拆得更细一点:
text
1) 读取 application.yml / application.properties
2) 创建 ApplicationContext(容器)
3) 扫描配置类(@SpringBootApplication / @Configuration)
4) 开始自动配置(AutoConfiguration 生效)
5) 注册 BeanDefinition
6) 创建 Bean 实例(构造方法)
7) 依赖注入(@Autowired)
8) Bean 初始化回调
- @PostConstruct
- InitializingBean.afterPropertiesSet
- 自定义 init-method
9) Context refresh 完成
10) 执行 Runner
- CommandLineRunner
- ApplicationRunner
11) 启动 Web Server(Tomcat/Netty)
12) 发布 ApplicationReadyEvent ✅ 最终阶段
关键点:Spring Boot 的"自动配置"可能在你之后继续修改某些 Bean
这就是为什么:
"太早 setCallback" 会被覆盖。
四、你在做什么?为什么这件事对时机敏感?
你在做的是:
java
rabbitTemplate.setConfirmCallback(...)
rabbitTemplate.setReturnsCallback(...)
这属于:
✅ 修改 RabbitTemplate 的全局行为
它不是"普通字段赋值",而是改变整个 MQ 投递链路的回调策略。
这种全局行为配置非常怕两件事:
- RabbitTemplate 还没完全初始化好
- RabbitTemplate 后续还会被 Spring Boot 自动配置"再加工"
五、为什么不能随便写在 @PostConstruct 里?
很多人第一反应是:
"我在 Bean 初始化完之后 setCallback,不就行了吗?"
比如:
java
@PostConstruct
public void init() {
rabbitTemplate.setConfirmCallback(...);
}
❌ 这可能会出的问题(真实项目常见)
回调被覆盖(最常见)
RabbitTemplate 是 Spring Boot 自动配置出来的组件,它可能在 后续自动配置阶段继续设置默认属性。
你在 @PostConstruct 设置回调后:
text
@PostConstruct 执行(你 setCallback)
↓
后续某个 AutoConfiguration 执行(重新 setCallback)
↓
你的回调失效(你还以为生效了)
这种问题的特点是:
- 不报错
- 只是"回调没触发"
- 非常难排查
多个 RabbitTemplate / 多个 ConnectionFactory 时混乱
如果项目后来引入:
- 多数据源 RabbitMQ
- 多个 RabbitTemplate
- 或者框架自动注册了另一个 template
你早期 set 的可能不是最终用于发送消息的那个 template。
六、为什么 ApplicationReadyEvent 是"黄金时间点"?
当 ApplicationReadyEvent 发布时:
✅ 所有自动配置已经执行完
✅ 你 setCallback 不会再被覆盖
✅ RabbitTemplate 已处于最终状态
✅ 应用已 ready,行为可预测
换句话说:
这是"房子装修完后再挂窗帘"的时刻。
七、用生活比喻
❌ 太早做(构造方法 / @PostConstruct)
你在装修阶段装窗帘
后面装修队:拆墙、刷漆、换窗户
窗帘可能被拆掉、挂歪、甚至换成默认窗帘
✅ ReadyEvent 做
装修完,家具都摆好了,钥匙交付
你再挂窗帘
这就是最终状态,不会再变
八、几种常见初始化方式对比
| 方式 | 触发时机 | 是否可能被自动配置覆盖 | 推荐程度 |
|---|---|---|---|
| 构造方法 | Bean 创建 | ✅ 非常可能 | ❌ |
| @PostConstruct | Bean 初始化 | ✅ 可能 | ⚠️ |
| InitializingBean | Bean 初始化 | ✅ 可能 | ⚠️ |
| CommandLineRunner | 容器启动后 | 很少 | ✅ |
| ✅ ApplicationReadyEvent | 应用完全就绪 | 几乎不会 | ✅✅✅ |
九、更推荐的工程写法(两种)
方案 A:现在这种(监听 ReadyEvent)
优点:
- 最直观
- 时机最稳
- 不影响自动配置
方案 B:用 RabbitTemplateConfigurer / Bean 后置增强(高级)
如果你想更"Spring 风格",也可以考虑:
- 自定义
RabbitTemplatebean(慎重) - 或用
SmartInitializingSingleton - 或用
@EventListener(ApplicationReadyEvent.class)
例如更简洁的写法:
java
@Component
@Slf4j
public class RabbitTemplateCallbackConfig {
private final RabbitTemplate rabbitTemplate;
public RabbitTemplateCallbackConfig(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
@EventListener(ApplicationReadyEvent.class)
public void init() {
rabbitTemplate.setConfirmCallback((correlationData, ack, reason) -> {
...
});
rabbitTemplate.setReturnsCallback(returned -> {
...
});
}
}
这个和你现在做的事本质一样:
同样是"等 ready 再设置"。
十、总结
RabbitTemplate 的回调属于全局行为配置,必须在自动配置结束后绑定。
所以用
ApplicationReadyEvent是最稳妥、最不踩坑的方式。