这三个注解是 Spring 容器管理 Bean 生命周期的核心工具。如果把 Bean 比作一个员工那么DependsOn是入职门槛必须签入职合同我才能入职。PostConstruct是入职仪式拿到电脑和账号后开机、登录、准备工作。PreDestroy是离职交接关电脑、交钥匙、保存文档。下面我将结合Spring Boot 实战场景详细拆解这三个注解的用法、原理和避坑指南。1. DependsOn控制“谁先谁后”核心作用强制指定 Bean 的初始化顺序。虽然 Spring 的Autowired能自动解决大部分依赖但在以下场景会失效必须用DependsOn隐式依赖副作用Bean A 没有直接持有 Bean B 的引用没有Autowired但 A 的初始化逻辑依赖于 B 产生的副作用比如 B 往系统属性里放了值或者 B 初始化了某个静态资源/单例注册中心。第三方库 Bean无法修改源码加Autowired但必须等它先启动。实战代码场景CacheManager必须等RedisConfig加载完配置副作用后才能启动但代码里没有直接注入RedisConfig。1import org.springframework.context.annotation.Bean; 2import org.springframework.context.annotation.Configuration; 3import org.springframework.context.annotation.DependsOn; 4import org.springframework.stereotype.Component; 5 6Component 7class RedisConfig { 8 public RedisConfig() { 9 System.out.println(1. RedisConfig 正在加载配置...); 10 try { Thread.sleep(1000); } catch (Exception e) {} // 模拟耗时 11 System.out.println( - RedisConfig 加载完毕); 12 } 13} 14 15Component 16DependsOn(redisConfig) // 【关键】强制 Spring 先创建 redisConfig哪怕没有注入它 17public class CacheManager { 18 19 public CacheManager() { 20 System.out.println(2. CacheManager 正在启动...); 21 // 此时可以确信 RedisConfig 已经初始化完成 22 System.out.println( - CacheManager 启动完毕); 23 } 24}注意值必须是对方的 Bean 名称默认是类名首字母小写如redisConfig。如果指定的 Bean 不存在Spring Boot 启动会直接报错BeanCreationException。2. PostConstruct初始化的“黄金位置”核心作用在 Bean 实例化并且所有依赖注入Autowired完成后执行。这是执行初始化逻辑如连接数据库、加载缓存、启动线程的最佳时机。为什么不用构造方法构造方法执行时Autowired的字段还是null无法使用。实战场景缓存预热应用启动时把热点数据从数据库加载到内存。启动定时任务启动一个后台线程池或调度器。资源检查检查数据库连接、文件路径是否存在。实战代码缓存预热与异步启动这是一个非常典型的 Spring Boot 业务场景。1import org.springframework.beans.factory.annotation.Autowired; 2import org.springframework.stereotype.Service; 3import javax.annotation.PostConstruct; 4import java.util.concurrent.Executors; 5import java.util.concurrent.ScheduledExecutorService; 6import java.util.concurrent.TimeUnit; 7 8Service 9public class DataCacheService { 10 11 Autowired 12 private UserRepository userRepository; // 假设这是数据库操作类 13 14 private ScheduledExecutorService scheduler; 15 16 // 1. 构造方法 17 public DataCacheService() { 18 System.out.println(构造方法此时 userRepository 是 null不能用); 19 } 20 21 // 2. PostConstruct依赖注入已完成可以安全使用 userRepository 22 PostConstruct 23 public void init() { 24 System.out.println(PostConstruct开始加载缓存...); 25 26 // 模拟耗时操作从数据库加载所有用户到内存 27 // ListUser users userRepository.findAll(); 28 System.out.println( - 缓存已加载 模拟数据); 29 30 // 启动后台定时任务每 10 秒刷新一次 31 scheduler Executors.newScheduledThreadPool(1); 32 scheduler.scheduleAtFixedRate(() - { 33 System.out.println( - [后台任务] 正在刷新缓存...); 34 }, 0, 10, TimeUnit.SECONDS); 35 36 System.out.println(PostConstruct初始化完成服务就绪); 37 } 38}3. PreDestroy资源释放的“最后一道防线”核心作用在 Spring 容器关闭前Bean 销毁前执行清理逻辑。核心价值防止内存泄漏保证优雅停机。实战场景关闭线程池防止应用停止后后台线程还在跑。关闭连接关闭数据库连接池、Redis 连接、文件流。数据落盘将内存中的临时统计数据写入数据库。实战代码优雅停机配合上面的DataCacheService我们需要在应用关闭时停止线程池。1import javax.annotation.PreDestroy; 2 3// 接上面的类 4 // ... 5 6 PreDestroy 7 public void destroy() { 8 System.out.println(PreDestroy应用正在关闭正在清理资源...); 9 10 if (scheduler ! null !scheduler.isShutdown()) { 11 scheduler.shutdown(); // 停止接收新任务 12 try { 13 // 等待任务结束最多等 5 秒 14 if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) { 15 scheduler.shutdownNow(); // 强制关闭 16 } 17 } catch (InterruptedException e) { 18 scheduler.shutdownNow(); 19 Thread.currentThread().interrupt(); 20 } 21 } 22 System.out.println(PreDestroy资源清理完毕再见); 23 }4. 重点JDK 11/17/21 与 Spring Boot 3 的替代方案在 JDK 9 之后javax.annotation模块被移除。Spring Boot 2.x (JDK 8): 使用javax.annotation.PostConstructSpring Boot 3.x (JDK 17/21): 使用jakarta.annotation.PostConstruct如果你不想处理包名问题或者想使用更 Spring 原生的方式可以使用以下替代方案替代方案 1Spring 原生接口无依赖问题这是 Spring 提供的标准接口不依赖任何外部规范完全兼容所有 JDK 版本。1import org.springframework.beans.factory.DisposableBean; 2import org.springframework.beans.factory.InitializingBean; 3import org.springframework.stereotype.Component; 4 5Component 6public class NativeLifecycleBean implements InitializingBean, DisposableBean { 7 8 Override 9 public void afterPropertiesSet() throws Exception { 10 // 替代 PostConstruct 11 System.out.println(InitializingBean: 初始化完成); 12 } 13 14 Override 15 public void destroy() throws Exception { 16 // 替代 PreDestroy 17 System.out.println(DisposableBean: 销毁开始); 18 } 19}替代方案 2EventListener推荐用于全局启动如果你不是要初始化某个 Bean而是想等整个应用启动完Tomcat 就绪再做某事这个比PostConstruct更好因为它解耦了业务逻辑。1import org.springframework.boot.context.event.ApplicationReadyEvent; 2import org.springframework.context.event.EventListener; 3import org.springframework.stereotype.Component; 4 5Component 6public class AppStartupListener { 7 8 EventListener(ApplicationReadyEvent.class) 9 public void onAppReady() { 10 System.out.println( 整个应用已完全启动可以开始接客了); 11 // 适合做发送启动通知、开始消费 MQ 消息 12 } 13}5. 综合避坑指南与注意事项A. 异常处理生死攸关PostConstruct抛异常 启动失败如果这个方法里抛出未捕获的异常比如数据库连不上Spring Boot 会认为应用不可用直接停止启动。建议在方法内部使用try-catch包裹关键逻辑记录日志或者决定是继续运行还是主动System.exit(1)。PreDestroy抛异常 忽略并继续如果销毁方法报错Spring 通常会记录错误日志然后继续销毁下一个 Bean。B. JDK 9 的依赖问题PostConstruct和PreDestroy属于javax.annotation包。JDK 8自带无需配置。JDK 11/17/21已被移除。如果你的项目运行在高版本 JDK 上必须在pom.xml中引入依赖否则会报ClassNotFoundException1dependency 2 groupIdjavax.annotation/groupId 3 artifactIdjavax.annotation-api/artifactId 4 version1.3.2/version 5/dependency(注Spring Boot 2.3 通常通过spring-boot-starter间接管理了这个依赖但如果是纯 Java 项目需注意)C. 执行顺序如果你混用了多种初始化方式它们的执行顺序是固定的构造方法Autowired注入PostConstructInitializingBean.afterPropertiesSet()(Spring 接口)init-method(XML 或 Bean 配置)建议统一使用PostConstruct不要混用否则代码很难维护。D. 静态方法无效PostConstruct只能修饰非静态方法。因为它是针对 Bean 实例对象的而静态方法属于类。E. 单例 vs 多例单例 (Singleton, 默认)PostConstruct在容器启动时执行一次PreDestroy在容器关闭时执行。多例 (Prototype)每次getBean都会执行PostConstruct但Spring 不会管理多例 Bean 的销毁所以PreDestroy不会执行总结表格注解核心用途执行时机常见坑DependsOn强制顺序实例化之前Bean 名称写错导致启动报错PostConstruct资源初始化注入完成后使用前抛异常导致启动失败JDK9 缺依赖PreDestroy资源清理容器关闭前多例 Bean 不执行kill -9杀进程不执行