Spring 容器不仅负责 Bean 的创建和依赖注入,还提供了多个扩展点,让开发者在 Bean 就绪时执行初始化逻辑,在容器关闭前完成资源清理。这些扩展点既有基于注解的、基于接口的,也有在配置中声明的方式。本小节将它们逐一展开,并厘清它们在同一个 Bean 中的执行顺序。
4.2.1 方式一:JSR-250 注解 —— @PostConstruct 与 @PreDestroy
这是最通用、最推荐的方式。@PostConstruct 标注的方法会在依赖注入完成后自动执行,用于执行初始化逻辑;@PreDestroy 标注的方法会在 Bean 被销毁前执行,用于释放资源。
@Component
public class DataCollector {
private List<String> buffer = new ArrayList<>();
@PostConstruct
public void init() {
System.out.println("DataCollector 初始化:预热缓存连接...");
// 加载基础配置、建立网络连接等
}
@PreDestroy
public void cleanup() {
System.out.println("DataCollector 销毁:刷新缓冲区、关闭连接...");
buffer.clear();
}
}
特点:
- 方法可以任意命名,访问权限推荐
public,但不能是static。 - 要求开启注解支持(Spring Boot 自动开启,传统 Spring 需
<context:annotation-config/>或@Configuration扫描)。 - 简单、直观,与 Spring 框架解耦(属于 Java 标准注解),可移植性好。
4.2.2 方式二:Spring 接口 —— InitializingBean 和 DisposableBean
通过实现 org.springframework.beans.factory.InitializingBean 接口的 afterPropertiesSet() 方法,或 DisposableBean 接口的 destroy() 方法,也能达成同样的目的。
@Component
public class ReportGenerator implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("ReportGenerator 初始化:加载报表模板...");
}
@Override
public void destroy() throws Exception {
System.out.println("ReportGenerator 销毁:清理临时文件...");
}
}
特点:
- 比注解更“重”,因为代码直接耦合了 Spring 的特定接口。
- 在一些旧项目中常见,现在官方也更推荐注解或配置方式。
- 方法签名固定,编译器会强制检查,但灵活性不如注解。
4.2.3 方式三:@Bean 注解的 initMethod 和 destroyMethod
在 @Configuration 类中使用 @Bean 声明 Bean 时,可以通过 initMethod 和 destroyMethod 指定自定义的初始化和销毁方法。这种方式特别适合管理第三方类库的 Bean,因为无法修改第三方源码加注解。
public class ExternalCacheManager {
public void start() {
System.out.println("ExternalCacheManager 启动");
}
public void stop() {
System.out.println("ExternalCacheManager 停止");
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "start", destroyMethod = "stop")
public ExternalCacheManager cacheManager() {
return new ExternalCacheManager();
}
}
特点:
- 完全非侵入,第三方类无需实现任何接口或添加注解。
destroyMethod有默认推断机制:当用@Bean声明时,Spring 会自动寻找名为close或shutdown的方法(可设置destroyMethod = ""禁用)。- 可以实现基于配置的灵活控制,比如在不同环境下指定不同的初始方法。
4.2.4 方式四:XML 配置中的 init-method 和 destroy-method
如果项目仍在使用 XML 配置,可以在 <bean> 标签上指定初始化和销毁方法:
<bean id="externalCache" class="com.example.ExternalCacheManager"
init-method="start" destroy-method="stop" />
这与 @Bean 的属性名一致,用途和场景完全相同,都是为普通 Java 对象赋予生命周期回调。
4.2.5 方式五:@Component 与 XML 混合(基本废弃)
早期的 Spring 允许在 @Component 注解中指定 initMethod 属性,但 Spring 5.x 之后已不建议使用,此处仅提及以避免疑惑。
4.2.6 多种方式的执行顺序
当一个 Bean 同时采用多种方式声明初始化或销毁逻辑时,Spring 按固定顺序依次调用它们。了解这个顺序对排查问题至关重要——尤其是当不同回调中依赖了彼此的状态。
初始化执行顺序:
@PostConstruct注解的方法。InitializingBean接口的afterPropertiesSet()。@Bean(initMethod)或 XMLinit-method指定的自定义方法。
实例验证:假设有一个 Bean 同时使用了三种方式:
@Component
public class LifecycleBean implements InitializingBean, DisposableBean {
@PostConstruct
public void postConstruct() {
System.out.println("1. @PostConstruct");
}
@Override
public void afterPropertiesSet() {
System.out.println("2. afterPropertiesSet");
}
public void customInit() {
System.out.println("3. customInit (来自 @Bean initMethod)");
}
@PreDestroy
public void preDestroy() {
System.out.println("1. @PreDestroy");
}
@Override
public void destroy() {
System.out.println("2. destroy");
}
public void customDestroy() {
System.out.println("3. customDestroy");
}
}
配合配置类:
@Bean(initMethod = "customInit", destroyMethod = "customDestroy")
public LifecycleBean lifecycleBean() {
return new LifecycleBean();
}
启动和关闭容器,控制台输出将严格遵循上述顺序。掌握这个顺序,实际开发中就可以自由组合不同方式而不会互相干扰。
4.2.7 真实场景中的选择建议
- 业务 Bean 的初始化:优先使用
@PostConstruct,代码最清晰且与框架解耦。 - 第三方类管理:采用
@Bean的initMethod/destroyMethod,不修改第三方源码。 - 遗留代码维护:若已有实现
InitializingBean的 Bean,了解执行顺序即可,不必强制重写。 - 避免混合使用:单一 Bean 中同时使用多种方式会增加理解成本,除非必要(比如既有注解又有 XML 遗留配置),否则只在一种方式上实现生命周期逻辑。
- 注意销毁回调触发条件:
@PreDestroy等只在容器正常关闭(close())时触发,如果 JVM 直接崩溃或kill -9,这些回调不会被调用。关键资源(如文件锁)仍需额外保障。
4.2.8 补充:BeanPostProcessor 层面的初始化钩子
除了 Bean 自身提供的回调,Spring 还允许通过 BeanPostProcessor 的 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法,在 Bean 初始化前后插入自定义逻辑。这些处理器作用于所有 Bean,常用于代理生成、属性校验等框架级行为。它们的执行时机在 @PostConstruct 和 afterPropertiesSet 的前后,属于容器级别而非单个 Bean 的回调。
现在,你已经掌握了 Bean 初始化与销毁的全部可用手段及其协同规则。在下一个部分,我们将考察 Bean 的作用域如何影响这些生命周期回调的调用时机。