使用 @PostConstruct 和 @PreDestroy (Using @PostConstruct and @PreDestroy)
CommonAnnotationBeanPostProcessor 不仅能识别 @Resource 注解,还能识别 JSR-250 生命周期注解:jakarta.annotation.PostConstruct 和 jakarta.annotation.PreDestroy。Spring 2.5 引入了对这些注解的支持,它们为初始化回调和销毁回调中描述的生命周期回调机制提供了另一种选择。只要在 Spring ApplicationContext 中注册了 CommonAnnotationBeanPostProcessor,标注了这些注解的方法就会在生命周期的特定点被调用,该点与 Spring 生命周期接口方法或显式声明的回调方法一致。
在下面的示例中,缓存将在初始化时预填充,并在销毁时清除:
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// 在初始化时填充电影缓存...
}
@PreDestroy
public void clearMovieCache() {
// 在销毁时清除电影缓存...
}
}class CachingMovieLister {
@PostConstruct
fun populateMovieCache() {
// 在初始化时填充电影缓存...
}
@PreDestroy
fun clearMovieCache() {
// 在销毁时清除电影缓存...
}
}有关组合各种生命周期机制产生的影响的详细信息,请参阅组合生命周期机制。
注意
与 @Resource 一样,@PostConstruct 和 @PreDestroy 注解类型在 JDK 6 到 8 中曾是标准 Java 类库的一部分。然而,整个 javax.annotation 包在 JDK 9 中从 Java 核心模块中分离出来,并最终在 JDK 11 中被移除。从 Jakarta EE 9 开始,该包现在位于 jakarta.annotation 中。如果需要,现在需要通过 Maven 中央仓库获取 jakarta.annotation-api 构件,并像任何其他库一样将其添加到应用程序的类路径中。
补充教学 —— 生命周期回调的“三重奏”
在 Spring 中,一个 Bean 的生命周期非常丰富。对于“初始化”和“销毁”这两个关键点,Spring 提供了三种方式。了解它们的执行顺序和优缺点是进阶必备。
1. 执行顺序(面试高频) 如果一个 Bean 同时使用了三种方式,它们的执行顺序如下:
初始化阶段:
- 标注了
@PostConstruct的方法。 InitializingBean接口的afterPropertiesSet()方法。- 自定义的
init-method(XML 中的init-method或@Bean(initMethod=...))。
- 标注了
销毁阶段:
- 标注了
@PreDestroy的方法。 DisposableBean接口的destroy()方法。- 自定义的
destroy-method。
- 标注了
2. 为什么推荐使用注解?
- 非侵入性:使用接口(如
InitializingBean)会将你的代码与 Spring 框架的 API 强耦合。而注解(尤其是 JSR-250 标准注解)更加通用。 - 代码整洁:你不需要在 XML 或 Java 配置类中去手动指定
init-method名称,直接在 Bean 内部就能声明逻辑。
3. 使用细节与注意事项
- 方法签名:标注了
@PostConstruct或@PreDestroy的方法不能有任何参数(除了某些拦截器场景,但通常在 IoC 容器中就是无参),且返回值通常为void。 - 访问修饰符:这些方法可以是
public、protected、package-private甚至private。Spring 都能找到并调用它们。 - 非静态:这些方法必须是非静态的。如果是静态方法,它将不会作为初始化回调被触发。
4. 现实案例:资源初始化 假设你编写一个文件处理服务,你需要在服务启动时确认某个文件夹是否存在:
@Component
public class FileService {
@Value("${upload.path}")
private String path;
@PostConstruct
public void init() {
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs(); // 确保目录存在
}
}
}这种逻辑放在构造函数里可能太早(因为 @Value 属性还没注入),放在业务代码里又太晚且重复。@PostConstruct 就是最完美的位置。