Spring 中的 @PostConstruct 和 @PreDestroy 注解的作用是什么?
- 标签:后端、Spring
🏛 业务架构师
@PostConstruct 和 @PreDestroy 是 Bean 生命周期的两个注解式钩子:
- @PostConstruct:在依赖注入完成之后、Bean 对外提供服务之前执行一次。用途:初始化内存字典、预热本地缓存、校验必填配置、注册回调——前提是"要用到注入进来的依赖",所以它比构造器合适(构造时依赖还没注入);
- @PreDestroy:容器关闭、Bean 销毁前执行。用途:释放连接池、关闭线程池、注销注册中心、向 MQ 发送终止信号——优雅停机的标配。
业务价值我最强调 @PreDestroy 在发布安全里的角色:K8s 滚动更新时 Pod 被销毁,如果直接杀死进程,在途请求断裂、数据库连接没释放、任务没做完标记。配合 server.shutdown=graceful + preStop 钩子,@PreDestroy 里的清理逻辑保证"处理完存量、不再接增量、干干净净下线"——这直接决定能不能在工作时间放心发布。
两个实战注意点:
- @PostConstruct 里不要做重 IO 的长耗时初始化(远程拉全量数据),会拖慢启动影响扩容,应该放到
ApplicationRunner异步化或用懒加载兜底; - 它和
InitializingBean/DisposableBean接口、init-method效果相同,但注解不耦合 Spring 接口、优先级最高,是团队规范的首选。
🏗 系统架构师
作用:Bean 生命周期回调(JSR-250):@PostConstruct 在依赖注入完成后执行,@PreDestroy 在容器关闭时执行。与 InitializingBean/DisposableBean 同类,但解耦于 Spring 接口,是推荐用法。
架构师看这两个注解的真正价值是「资源对称管理」:连接、线程池、文件句柄的创建与释放成对出现在相邻位置,避免初始化散落构造器、清理依赖零散 shutdown hook 的漂移风险。
生产注意点:
- 不要在
@PostConstruct里做重活——它阻塞启动主线程,耗时初始化(预热、大缓存加载)应放 ApplicationReadyEvent 或 Runner,并评估是否该后移到 readiness 打开之后; - 优雅停机靠
@PreDestroy链路:K8s SIGTERM → 摘流 → 容器 close 触发销毁回调(关线程池、flush 缓冲、提交 offset)→ 退出,预算控制在 terminationGracePeriodSeconds 内。漏掉任何一步,滚动发布就丢数据; - 初始化失败要让它直接失败——启动失败好过带病运行。
💻 全栈开发专家
@PostConstruct:依赖注入完成后、Bean 投入使用前执行一次,做初始化;@PreDestroy:容器销毁前执行,做资源释放。注意 Boot 3.x 起包名从 javax.annotation 变成 jakarta.annotation(迁移时全局替换),由 CommonAnnotationBeanPostProcessor 触发。
java
@Component
public class DictCache {
private final DictMapper dictMapper;
private Map<String, String> cache;
public DictCache(DictMapper dictMapper) { this.dictMapper = dictMapper; }
@PostConstruct
public void warmUp() {
this.cache = dictMapper.loadAll().stream()
.collect(Collectors.toMap(Dict::code, Dict::label));
if (cache.isEmpty()) throw new IllegalStateException("字典为空,禁止启动");
}
@PreDestroy
public void shutdown() { log.info("dict cache destroyed"); }
}要点:@PostConstruct 执行时依赖已注入完(对比构造器时点),且抛异常会中断启动——我用这个特性做配置兜底校验,带病启动不如早点死。时序链:构造器 → @PostConstruct → afterPropertiesSet → initMethod。
坑:@PostConstruct 里干重活会拖慢启动(大缓存预热挪到 ApplicationRunner 异步做,K8s 里启动慢会被探针杀掉)。