Appearance
Spring Boot 生命周期管理:@PostConstruct 和 @PreDestroy 注解详解
🎯 为什么需要生命周期管理?
在 Spring Boot 应用开发中,我们经常需要在 Bean 创建后执行一些初始化操作,或在 Bean 销毁前进行清理工作。比如:
- 🔧 初始化缓存数据
- 🌐 建立数据库连接池
- 📁 创建临时文件或目录
- 🧹 释放资源和清理工作
如果没有合适的生命周期管理机制,我们可能会遇到以下问题:
常见问题
- 资源泄漏:忘记关闭文件流、数据库连接等
- 初始化时机不当:在依赖注入完成前就尝试使用依赖
- 内存泄漏:缓存数据没有及时清理
💡 @PostConstruct 和 @PreDestroy 的设计哲学
Spring 提供了 @PostConstruct
和 @PreDestroy
注解来优雅地解决生命周期管理问题:
- @PostConstruct:在依赖注入完成后,Bean 准备就绪时执行
- @PreDestroy:在 Bean 即将被销毁前执行清理工作
这两个注解遵循 JSR-250 标准,提供了一种标准化、声明式的生命周期管理方式。
🛠️ 基础用法示例
让我们通过一个缓存管理的例子来理解这两个注解的使用:
kotlin
import jakarta.annotation.PostConstruct
import jakarta.annotation.PreDestroy
import org.springframework.stereotype.Component
import java.util.concurrent.ConcurrentHashMap
@Component
class MovieCacheManager {
private val movieCache = ConcurrentHashMap<String, Movie>()
@PostConstruct
fun initializeCache() {
println("🚀 初始化电影缓存...")
// 预加载热门电影数据
loadPopularMovies()
println("✅ 缓存初始化完成,已加载 ${movieCache.size} 部电影")
}
@PreDestroy
fun cleanupCache() {
println("🧹 清理电影缓存...")
// 保存缓存统计信息
saveCacheStatistics()
// 清空缓存
movieCache.clear()
println("✅ 缓存清理完成")
}
private fun loadPopularMovies() {
// 模拟从数据库加载热门电影
movieCache["1"] = Movie("阿凡达", "科幻")
movieCache["2"] = Movie("泰坦尼克号", "爱情")
movieCache["3"] = Movie("复仇者联盟", "动作")
}
private fun saveCacheStatistics() {
// 模拟保存缓存使用统计
println("📊 缓存命中率: 85%,总访问次数: 1000")
}
fun getMovie(id: String): Movie? = movieCache[id]
}
data class Movie(val title: String, val genre: String)
🔄 实际业务场景应用
场景一:数据库连接池管理
kotlin
@Component
class DatabaseManager {
private var dataSource: HikariDataSource? = null
// ❌ 问题:不知道何时初始化,可能在依赖注入前就被调用
fun initDataSource() {
dataSource = HikariDataSource().apply {
jdbcUrl = "jdbc:mysql://localhost:3306/mydb"
username = "user"
password = "password"
}
}
// ❌ 问题:容易忘记调用,导致连接泄漏
fun closeDataSource() {
dataSource?.close()
}
}
kotlin
@Component
class DatabaseManager {
@Value("${spring.datasource.url}")
private lateinit var jdbcUrl: String
@Value("${spring.datasource.username}")
private lateinit var username: String
private var dataSource: HikariDataSource? = null
@PostConstruct
fun initDataSource() {
println("🔗 初始化数据库连接池...")
dataSource = HikariDataSource().apply {
this.jdbcUrl = this@DatabaseManager.jdbcUrl
this.username = this@DatabaseManager.username
maximumPoolSize = 10
}
println("✅ 数据库连接池初始化完成")
}
@PreDestroy
fun closeDataSource() {
println("🔒 关闭数据库连接池...")
dataSource?.close()
println("✅ 数据库连接池已关闭")
}
fun getConnection(): Connection? = dataSource?.connection
}
场景二:定时任务管理
kotlin
import jakarta.annotation.PostConstruct
import jakarta.annotation.PreDestroy
import org.springframework.stereotype.Component
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
@Component
class ScheduledTaskManager {
private lateinit var scheduler: ScheduledExecutorService
@PostConstruct
fun startScheduledTasks() {
println("⏰ 启动定时任务...")
scheduler = Executors.newScheduledThreadPool(2)
// 每5分钟清理临时文件
scheduler.scheduleAtFixedRate({
cleanupTempFiles()
}, 0, 5, TimeUnit.MINUTES)
// 每小时生成报告
scheduler.scheduleAtFixedRate({
generateHourlyReport()
}, 1, 1, TimeUnit.HOURS)
println("✅ 定时任务启动完成")
}
@PreDestroy
fun shutdownScheduledTasks() {
println("🛑 关闭定时任务...")
scheduler.shutdown()
try {
if (!scheduler.awaitTermination(10, TimeUnit.SECONDS)) {
scheduler.shutdownNow()
}
} catch (e: InterruptedException) {
scheduler.shutdownNow()
Thread.currentThread().interrupt()
}
println("✅ 定时任务已关闭")
}
private fun cleanupTempFiles() {
println("🗑️ 清理临时文件...")
}
private fun generateHourlyReport() {
println("📈 生成小时报告...")
}
}
⚠️ 使用注意事项
重要提醒
从 JDK 9 开始,javax.annotation
包被移除,需要使用 jakarta.annotation
包。
Maven 依赖配置
如果你的项目中没有这些注解,需要添加依赖:
xml
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
方法签名要求
方法签名限制
- 方法必须是
public
或protected
- 方法不能有参数
- 方法不能有返回值(void)
- 方法不能是
static
kotlin
@Component
class ExampleService {
@PostConstruct
fun correctInit() { // ✅ 正确
// 初始化逻辑
}
@PostConstruct
private fun wrongInit() { // ❌ 错误:private 方法
// 不会被调用
}
@PostConstruct
fun wrongInitWithParam(param: String) { // ❌ 错误:有参数
// 不会被调用
}
@PostConstruct
fun wrongInitWithReturn(): String { // ❌ 错误:有返回值
return "error"
}
}
🔄 与其他生命周期机制的结合
Spring 提供了多种生命周期管理方式,它们的执行顺序如下:
完整的生命周期示例代码
kotlin
import jakarta.annotation.PostConstruct
import jakarta.annotation.PreDestroy
import org.springframework.beans.factory.DisposableBean
import org.springframework.beans.factory.InitializingBean
import org.springframework.stereotype.Component
@Component
class LifecycleDemo : InitializingBean, DisposableBean {
init {
println("1️⃣ 构造函数执行")
}
@PostConstruct
fun postConstruct() {
println("4️⃣ @PostConstruct 执行")
}
override fun afterPropertiesSet() {
println("5️⃣ InitializingBean.afterPropertiesSet() 执行")
}
@PreDestroy
fun preDestroy() {
println("8️⃣ @PreDestroy 执行")
}
override fun destroy() {
println("9️⃣ DisposableBean.destroy() 执行")
}
}
🎯 最佳实践建议
1. 优先使用注解方式
推荐做法
相比实现接口或 XML 配置,注解方式更简洁、更直观,是首选方案。
2. 异常处理
kotlin
@Component
class RobustService {
@PostConstruct
fun initialize() {
try {
// 初始化逻辑
connectToExternalService()
} catch (e: Exception) {
println("❌ 初始化失败: ${e.message}")
// 记录日志,但不要抛出异常,避免影响应用启动
}
}
@PreDestroy
fun cleanup() {
try {
// 清理逻辑
disconnectFromExternalService()
} catch (e: Exception) {
println("⚠️ 清理过程中出现异常: ${e.message}")
// 记录日志,继续执行其他清理工作
}
}
private fun connectToExternalService() {
// 连接外部服务
}
private fun disconnectFromExternalService() {
// 断开外部服务连接
}
}
3. 避免长时间阻塞
性能考虑
@PostConstruct
方法会阻塞应用启动,@PreDestroy
方法会阻塞应用关闭。避免在这些方法中执行耗时操作。
kotlin
@Component
class AsyncInitService {
@Autowired
private lateinit var taskExecutor: TaskExecutor
@PostConstruct
fun quickInit() {
// 快速初始化必要资源
initializeEssentialResources()
// 异步执行耗时的初始化工作
taskExecutor.execute {
initializeHeavyResources()
}
}
private fun initializeEssentialResources() {
// 快速初始化
}
private fun initializeHeavyResources() {
// 耗时的初始化工作
}
}
📝 总结
@PostConstruct
和 @PreDestroy
注解为 Spring Boot 应用提供了优雅的生命周期管理机制:
✅ 优势:
- 标准化的 JSR-250 规范
- 声明式编程,代码简洁
- 自动管理,减少人为错误
- 与 Spring 容器完美集成
🎯 适用场景:
- 缓存初始化和清理
- 资源连接的建立和释放
- 定时任务的启动和停止
- 配置文件的加载和保存
通过合理使用这两个注解,我们可以让 Spring Boot 应用更加健壮、可靠,避免资源泄漏等常见问题。记住:好的生命周期管理是构建高质量应用的基础! 🚀