Spring Bean 的初始化和销毁回调有哪几种方式?它们的执行顺序是什么?
简化版
Spring 提供了三种「初始化回调」和三种对应的「销毁回调」,让你在 Bean 创建好(依赖注入完成)之后、或销毁之前执行自定义逻辑。三种初始化方式:① @PostConstruct(JSR-250 注解,标在方法上,最推荐、最简洁);② 实现 InitializingBean 接口(重写 afterPropertiesSet(),与 Spring 耦合);③ @Bean(initMethod="...") 或 XML 的 init-method(指定一个初始化方法,与 Spring 解耦,适合第三方类)。执行顺序(重点):@PostConstruct → InitializingBean.afterPropertiesSet() → init-method。销毁对称:@PreDestroy → DisposableBean.destroy() → destroy-method。注意点:① 这些回调都在「依赖注入完成后」才执行(所以能安全用注入的字段);② 销毁回调只对单例生效,且要容器正常关闭才触发(prototype Bean 的销毁 Spring 不管);③ 三种能力等价,实践中优先用 @PostConstruct/@PreDestroy(简洁、标准),第三方类用 initMethod。
详细版
三种初始化 / 销毁回调对比:
| 方式 | 初始化 | 销毁 | 与 Spring 耦合 | 推荐度 |
|---|---|---|---|---|
| 注解 | @PostConstruct | @PreDestroy | 无(JSR-250 标准) | ⭐ 最推荐 |
| 接口 | InitializingBean.afterPropertiesSet() | DisposableBean.destroy() | 强(要 implements) | 一般 |
| 指定方法 | @Bean(initMethod) / init-method | @Bean(destroyMethod) / destroy-method | 无(配置在外部) | 第三方类用 |
执行顺序:
Bean 创建流程里的位置:
实例化 → 属性注入(依赖注入) → Aware 回调
→ BeanPostProcessor.postProcessBeforeInitialization
→ ★ @PostConstruct
→ ★ InitializingBean.afterPropertiesSet()
→ ★ init-method
→ BeanPostProcessor.postProcessAfterInitialization(AOP 代理在这)
→ Bean 就绪
销毁(容器关闭时,单例):
→ ★ @PreDestroy
→ ★ DisposableBean.destroy()
→ ★ destroy-method
@Component
public class MyBean implements InitializingBean, DisposableBean {
@Autowired private SomeDep dep;
@PostConstruct // ① 最先执行
public void init1() { /* 用 dep 没问题,注入已完成 */ }
@Override
public void afterPropertiesSet() { } // ② 其次(InitializingBean)
// ③ 最后(配 @Bean(initMethod="init3") 或 xml init-method)
public void init3() { }
@PreDestroy public void cleanup() { } // 销毁时最先
@Override public void destroy() { } // 销毁其次
}
⚠️ 两个高频易错点:① 销毁回调只对单例、且容器正常关闭时才触发——
prototype作用域的 Bean,Spring 创建后就「不管了」(不跟踪、不调销毁回调),你得自己负责清理;即使是单例,如果 JVM 被kill -9强杀、或没优雅关闭容器,@PreDestroy也不会执行(Spring Boot 里靠 JVM 关闭钩子触发,所以要让应用优雅停机)。② 回调时机是「依赖注入完成后」——所以@PostConstruct里可以安全使用@Autowired注入的字段(它们此时已经注入好了);但不要在构造器里用注入的字段(构造器执行时字段还没注入,会 NPE)——需要「拿到依赖后做初始化」就放@PostConstruct。
完整版教学
一、为什么需要初始化/销毁回调
先理解「为什么不在构造器里做初始化」:
一个 Bean 常需要"创建好之后做点准备工作":
- 校验必需的配置是否齐全
- 用注入的依赖做初始化(如预加载数据、建立连接、启动线程)
- 注册自己到某个管理器
为什么不放构造器?
构造器执行时,依赖还没注入!(Spring 是先 new 再注入属性)
→ 构造器里用 @Autowired 的字段 = NPE
所以需要一个"依赖注入完成后"的钩子:
→ 初始化回调(@PostConstruct 等)
在这里,注入的字段都就绪了,能安全使用
对称地,Bean 销毁前也常需要"收尾":
- 关闭连接、释放资源、停止线程、刷新缓冲区
→ 销毁回调(@PreDestroy 等)
初始化回调的必要性在于「构造器执行时依赖还没注入」——Spring 是「先 new 实例、再注入属性」,所以构造器里用 @Autowired 字段会 NPE。需要一个「依赖注入完成后」的钩子来做初始化(用注入的依赖预加载、建连接)。销毁回调对称——Bean 销毁前收尾(关连接、释放资源)。理解「构造器时依赖没注入不能用、初始化回调在注入完成后执行能安全用依赖、销毁回调用于收尾」,就理解了这些回调存在的意义。
二、方式一:@PostConstruct / @PreDestroy(最推荐)
第一种也是最推荐的方式是「JSR-250 注解」:
@PostConstruct:标在方法上,Bean 初始化时调用
@PreDestroy:标在方法上,Bean 销毁前调用
@PostConstruct
public void init() { /* 初始化逻辑 */ }
@PreDestroy
public void cleanup() { /* 清理逻辑 */ }
优点:
① 简洁(一个注解,不用 implements、不用配置)
② 标准(JSR-250 是 Java 标准注解,不是 Spring 专有)
→ 换到别的框架(如 CDI)也认识,代码不绑死 Spring
③ 语义清晰(方法名自定义,一看就懂)
注意:JDK 9+ 后 javax.annotation 被移出 JDK
Spring Boot 里通常已包含依赖,或用 jakarta.annotation(新版)
→ 这是它唯一的小麻烦
@PostConstruct/@PreDestroy 是「JSR-250 标准注解」——标在方法上即可。它是最推荐的方式:简洁(无需 implements/配置)、标准(Java 标准注解、不绑死 Spring)、语义清晰(方法名自定义)。唯一小麻烦是 JDK 9+ 后 javax.annotation 被移出 JDK(用 jakarta.annotation 或加依赖)。理解「@PostConstruct/@PreDestroy 是 JSR-250 标准注解、最推荐(简洁/标准/不绑死Spring)、注意 JDK9+ 包的变化」,就掌握了首选方式。
三、方式二:InitializingBean / DisposableBean 接口
第二种是「实现 Spring 的接口」:
InitializingBean 接口:afterPropertiesSet()
DisposableBean 接口:destroy()
public class MyBean implements InitializingBean, DisposableBean {
public void afterPropertiesSet() { /* 初始化 */ }
public void destroy() { /* 销毁 */ }
}
优点:
① 不用反射找方法(框架直接调接口方法,理论上略快)
② 方法名固定,明确
缺点:
★ 与 Spring 强耦合——你的业务类要 implements Spring 的接口
→ 业务代码"侵入"了框架,不够干净
→ 一般不推荐(除非写框架/基础组件)
命名细节:afterPropertiesSet 这个名字点明了时机——
"在属性设置(依赖注入)完成之后",正好呼应"注入后才回调"
InitializingBean(afterPropertiesSet)/DisposableBean(destroy)是「实现 Spring 接口」的方式。优点是框架直接调接口方法(不用反射找方法)、名字固定。缺点是与 Spring 强耦合——业务类要 implements 框架接口,侵入性强,一般不推荐(除非写框架/基础组件)。afterPropertiesSet 这名字恰好点明「属性设置完成后」的时机。理解「InitializingBean/DisposableBean 是实现 Spring 接口、优点是直接调不用反射、缺点是强耦合侵入业务代码不推荐」,就掌握了第二种方式。
四、方式三:init-method / destroy-method(第三方类)
第三种是「指定一个方法名」——配置在 Bean 定义外部:
XML:<bean init-method="init" destroy-method="cleanup"/>
Java 配置:@Bean(initMethod = "init", destroyMethod = "cleanup")
@Bean(initMethod = "start", destroyMethod = "stop")
public SomeThirdPartyBean bean() { return new SomeThirdPartyBean(); }
优点:
★ 与 Spring 完全解耦——被管理的类里没有任何 Spring 的东西
→ 特别适合"第三方类":你无法给它加 @PostConstruct 或让它 implements
(比如引入的库里的某个类),但能在 @Bean 里指定它的初始化/销毁方法
destroyMethod 的智能默认:
@Bean 的 destroyMethod 默认会"推断"——如果 Bean 有 close() 或 shutdown()
方法,会自动当作销毁方法调用(如数据源 DataSource 的 close)
→ 想禁用推断:destroyMethod = ""
init-method/destroy-method(@Bean(initMethod/destroyMethod))是「指定方法名」——配置在 Bean 定义处。最大优点是与 Spring 完全解耦(被管理的类里没有任何 Spring 依赖),特别适合第三方类(无法给它加注解或让它 implements 接口,但能在 @Bean 里指定其初始化/销毁方法)。@Bean 的 destroyMethod 还有智能推断(自动识别 close()/shutdown())。理解「init-method/destroy-method 指定方法名、与 Spring 完全解耦、适合第三方类、@Bean 的 destroyMethod 会推断 close/shutdown」,就掌握了第三种方式。
五、执行顺序:三种初始化的先后
当三种方式同时存在时,执行顺序是高频考点:
初始化执行顺序(三种同时存在时):
1. @PostConstruct (最先)
2. InitializingBean.afterPropertiesSet()
3. init-method (最后)
记忆:注解 → 接口 → 配置方法
(越"标准/靠近 Bean 本身"的越先,越"外部配置"的越后)
销毁执行顺序(对称):
1. @PreDestroy (最先)
2. DisposableBean.destroy()
3. destroy-method (最后)
在整个 Bean 生命周期里的位置:
实例化 → 属性注入 → Aware 回调
→ BeanPostProcessor 前置处理
→ 【@PostConstruct → afterPropertiesSet → init-method】
→ BeanPostProcessor 后置处理(AOP 代理生成)
→ Bean 就绪使用
→ (容器关闭)【@PreDestroy → destroy → destroy-method】
执行顺序:初始化 @PostConstruct → InitializingBean.afterPropertiesSet() → init-method,销毁对称 @PreDestroy → DisposableBean.destroy() → destroy-method。记忆法「注解 → 接口 → 配置方法」。注意 @PostConstruct 由 BeanPostProcessor(CommonAnnotationBeanPostProcessor)处理,在初始化阶段最先执行。理解「初始化顺序 @PostConstruct→afterPropertiesSet→init-method、销毁对称、记忆’注解→接口→配置方法’」,就掌握了这个高频考点。
六、销毁回调的陷阱:单例、prototype、优雅停机
销毁回调有几个容易踩的陷阱,必须讲清:
陷阱一:prototype Bean 的销毁回调不执行
Spring 对 prototype Bean:"创建并交给你后就不管了"
→ 不跟踪它的生命周期,不调销毁回调(@PreDestroy 等)
→ prototype 的资源清理要你自己负责
陷阱二:销毁回调需要"容器正常关闭"才触发
单例的销毁回调,在 ApplicationContext.close() 时才调
Spring Boot 里靠 JVM 关闭钩子(ShutdownHook)触发
→ 如果 kill -9 强杀进程、或容器没优雅关闭 → @PreDestroy 不执行!
→ 要让应用"优雅停机"(graceful shutdown),销毁回调才可靠
陷阱三:别在销毁回调里依赖"还没销毁的其他 Bean"
销毁是有顺序的,被依赖的 Bean 后销毁;但复杂依赖下要小心
实践建议:
关键资源清理(关连接、刷盘)放 @PreDestroy,
但也要有"兜底"(如连接池自身的超时回收),
不能完全指望 @PreDestroy 一定执行
销毁回调的三个陷阱:① prototype Bean 不执行销毁回调(Spring 创建后不跟踪,资源清理自己负责);② 需要容器正常关闭才触发(kill -9 强杀或非优雅关闭时 @PreDestroy 不执行,要优雅停机);③ 销毁顺序要注意依赖。所以关键清理放 @PreDestroy 但要有兜底(不能完全指望它一定执行)。这些是生产中容易踩的坑。理解「prototype 不调销毁回调、销毁回调需容器优雅关闭才触发(kill -9 不执行)、要优雅停机且有兜底」,就掌握了销毁回调的陷阱。
记忆钩子:「三种初始化回调:① @PostConstruct(JSR-250 注解,最推荐:简洁/标准/不绑死Spring) ② InitializingBean.afterPropertiesSet()(实现接口,强耦合侵入不推荐) ③ @Bean(initMethod)/init-method(指定方法名,与Spring解耦,适合第三方类,destroyMethod 会推断 close/shutdown);执行顺序 @PostConstruct→afterPropertiesSet→init-method(注解→接口→配置方法),销毁对称 @PreDestroy→destroy→destroy-method;回调在’依赖注入完成后’执行(所以能用注入字段、别在构造器用);销毁陷阱:prototype 不调、需容器优雅关闭才触发(kill -9 不执行)」。
七、常见误区与追问
- 误区:初始化逻辑应该放构造器里。 不行——Spring 先 new 实例再注入属性,构造器执行时 @Autowired 字段还没注入,用它会 NPE;「拿到依赖后做初始化」要放 @PostConstruct(依赖注入完成后才执行)。
- 误区:三种初始化方式效果不同。 能力等价(都在依赖注入后执行初始化),区别在耦合度和适用场景:@PostConstruct 最推荐(简洁标准)、InitializingBean 强耦合、init-method 适合第三方类;只是同时存在时有执行顺序。
- 误区:@PreDestroy 一定会执行。 不一定——只对单例、且容器正常关闭时才触发;prototype Bean 根本不调销毁回调;kill -9 强杀或非优雅关闭时单例的 @PreDestroy 也不执行;关键清理要有兜底。
- 误区:prototype 的 Bean 也会自动调用销毁回调。 不会——Spring 对 prototype 只负责创建,创建后不再跟踪其生命周期,不调用任何销毁回调;prototype 的资源清理要开发者自己负责。
- 追问:@PostConstruct、afterPropertiesSet、init-method 的执行顺序? @PostConstruct → InitializingBean.afterPropertiesSet() → init-method(记忆:注解→接口→配置方法);销毁对称 @PreDestroy → DisposableBean.destroy() → destroy-method。
- 追问:为什么初始化回调能安全使用 @Autowired 的字段,而构造器不能? 因为回调时机在「属性注入(依赖注入)完成之后」——Spring 生命周期是实例化→属性注入→初始化回调,所以回调里字段已注入;构造器在实例化阶段执行,此时属性还没注入,字段是 null。
- 追问:@Bean(destroyMethod) 有什么智能行为? 默认会「推断」销毁方法——如果 Bean 有 public 的 close() 或 shutdown() 方法,Spring 会自动把它当销毁方法调用(如 DataSource、连接池的 close);不想要这个推断可以显式设 destroyMethod = "" 禁用。
八、加强记忆
Spring 提供三种「初始化回调」和对称的三种「销毁回调」,在 Bean 依赖注入完成后(初始化)或销毁前执行自定义逻辑。三种初始化方式:① @PostConstruct(JSR-250 标准注解,标在方法上,最推荐——简洁、标准、不绑死 Spring;注意 JDK9+ 用 jakarta.annotation);② 实现 InitializingBean 接口(afterPropertiesSet(),与 Spring 强耦合、侵入业务代码,一般不推荐);③ @Bean(initMethod) / init-method(指定方法名,与 Spring 完全解耦、适合第三方类;@Bean 的 destroyMethod 会智能推断 close/shutdown)。执行顺序:初始化 @PostConstruct → afterPropertiesSet() → init-method(注解→接口→配置方法),销毁对称 @PreDestroy → destroy() → destroy-method。关键点:① 回调在「依赖注入完成后」执行——所以能安全用 @Autowired 字段,别在构造器里用注入字段(那时还没注入会 NPE);② 销毁回调只对单例、且容器正常关闭才触发——prototype Bean 不调销毁回调(自己清理),kill -9 强杀或非优雅关闭时单例的 @PreDestroy 也不执行(要优雅停机且有兜底)。实践优先用 @PostConstruct/@PreDestroy,第三方类用 initMethod/destroyMethod。一句话「三种初始化回调:@PostConstruct(注解,最推荐)/InitializingBean.afterPropertiesSet(接口,强耦合)/init-method(指定方法,适合第三方类);顺序注解→接口→配置方法,销毁对称;回调在依赖注入后(别在构造器用注入字段);销毁只对单例且需优雅关闭,prototype 不调」。