← 返回题目列表

Spring Bean 的初始化和销毁回调有哪几种方式?它们的执行顺序是什么?

中等 第 25 / 30 题 更新于 2026/07/28
初始化回调销毁回调PostConstructInitializingBean

简化版

Spring 提供了三种「初始化回调」和三种对应的「销毁回调」,让你在 Bean 创建好(依赖注入完成)之后、或销毁之前执行自定义逻辑。三种初始化方式:@PostConstruct(JSR-250 注解,标在方法上,最推荐、最简洁);② 实现 InitializingBean 接口(重写 afterPropertiesSet(),与 Spring 耦合);@Bean(initMethod="...") 或 XML 的 init-method(指定一个初始化方法,与 Spring 解耦,适合第三方类)。执行顺序(重点)@PostConstructInitializingBean.afterPropertiesSet()init-method。销毁对称:@PreDestroyDisposableBean.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 这个名字点明了时机——
  "在属性设置(依赖注入)完成之后",正好呼应"注入后才回调"

InitializingBeanafterPropertiesSet)/DisposableBeandestroy)是「实现 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 里指定其初始化/销毁方法)。@BeandestroyMethod 还有智能推断(自动识别 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】

执行顺序:初始化 @PostConstructInitializingBean.afterPropertiesSet()init-method,销毁对称 @PreDestroy → DisposableBean.destroy() → destroy-method。记忆法「注解 → 接口 → 配置方法」。注意 @PostConstructBeanPostProcessorCommonAnnotationBeanPostProcessor)处理,在初始化阶段最先执行。理解「初始化顺序 @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 完全解耦、适合第三方类@BeandestroyMethod 会智能推断 close/shutdown)。执行顺序:初始化 @PostConstructafterPropertiesSet()init-method(注解→接口→配置方法),销毁对称 @PreDestroydestroy()destroy-method关键点① 回调在「依赖注入完成后」执行——所以能安全用 @Autowired 字段,别在构造器里用注入字段(那时还没注入会 NPE);② 销毁回调只对单例、且容器正常关闭才触发——prototype Bean 不调销毁回调(自己清理),kill -9 强杀或非优雅关闭时单例的 @PreDestroy 也不执行(要优雅停机且有兜底)。实践优先用 @PostConstruct/@PreDestroy,第三方类用 initMethod/destroyMethod。一句话「三种初始化回调:@PostConstruct(注解,最推荐)/InitializingBean.afterPropertiesSet(接口,强耦合)/init-method(指定方法,适合第三方类);顺序注解→接口→配置方法,销毁对称;回调在依赖注入后(别在构造器用注入字段);销毁只对单例且需优雅关闭,prototype 不调」。