別再守舊!Spring Boot 替代 @PostConstruct 和 @PreDestroy 方案
環境:SpringBoot3.4.2
1. 簡介
在 Spring Boot 開發中,@PostConstruct 和 @PreDestroy 是兩個重要的生命周期注解,用于管理 Bean 的初始化和清理邏輯。
- @PostConstruct:此注解用于標記一個方法,該方法會在 Bean 的依賴注入完成后立即執行。它常用于執行初始化操作,比如加載配置、打開資源等。例如,在數據庫連接池初始化后,可以用它來驗證連接是否可用。
- @PreDestroy:此注解標記的方法會在 Bean 被銷毀前執行。它用于執行清理操作,比如關閉文件、釋放數據庫連接等。例如,在應用程序關閉時,可以用它來確保所有數據庫連接都被正確關閉,避免資源泄漏。
這兩個注解簡化了 Bean 生命周期的管理,使代碼更加清晰和易于維護。
本篇文章中我們將詳細介紹@PostConstruct、@PreDestroy 的替代方案。
2.實戰案例
2.1 什么是 @PostConstruct?
@PostConstruct 是 Spring Boot 中的一個注解,它指示 Spring 在 Bean 被創建且依賴注入完成后,但在 Bean 被使用之前運行特定的方法。
何時使用它?
當你想在應用程序開始處理請求之前初始化一些數據或執行設置任務時,就會用到它。
不使用 @PostConstruct 的示例(存在的問題)
假設你有一個 UserService,它負責從數據庫加載用戶信息。
@Service
public class UserService {
private final UserRepository userRepository;
private Map<Long, User> userCache = new HashMap<>();
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
return userCache.get(id);
}
}問題:應用程序啟動時,userCache 為空。
使用 @PostConstruct 初始化加載數據
為了在緩存中預加載用戶數據,我們使用 @PostConstruct 注解。
@Service
public class UserService {
private final UserRepository userRepository;
private Map<Long, User> userCache = new HashMap<>();
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@PostConstruct
public void init() {
userRepository.findAll().forEach(user -> userCache.put(user.getId(), user));
}
public User getUserById(Long id) {
return userCache.get(id);
}
}工作原理:
- Spring 創建 UserService Bean
- 容器自動將 UserRepository 注入到 UserService 中
- @PostConstruct 注解的 init() 方法會自動運行,將用戶數據加載到緩存中
注意,從 Java 9+ 開始,@PostConstruct 注解雖從 javax.annotation 包中被移除,但作為 Jakarta EE 轉型的一部分,它已被遷移至 jakarta.annotation 包。這意味著在 Spring Boot 3+ 中,@PostConstruct 和 @PreDestroy 注解仍然可用,只是它們現在位于 jakarta.annotation 命名空間下。因此,它們仍可用于初始化和清理任務,只是現在遵循的是 Jakarta EE 標準。另外,你也可以使用 Spring 的 @EventListener 注解結合 ApplicationReadyEvent 來實現類似的功能。
什么是 @EventListener(ApplicationReadyEvent.class)?
ApplicationReadyEvent 該事件用于在應用程序完全啟動并準備好處理請求后運行代碼。
與 @PostConstruct 不同,@PostConstruct 是在單個 Bean 初始化完成后運行,而 @EventListener(ApplicationReadyEvent.class) 是在整個 Spring 應用程序上下文完全初始化完成后才運行。
為什么要使用 @EventListener(ApplicationReadyEvent.class)?
- 它確保你的初始化邏輯只在整個應用程序準備就緒后才運行
- 它對于以下任務非常有用:
將數據加載到數據庫中
啟動后臺進程
驗證配置
發送啟動通知
示例1:使用 @EventListener(ApplicationReadyEvent.class)
以下是如何使用 @EventListener(ApplicationReadyEvent.class) 來替代 @PostConstruct 的方法:
@Component
public class PackStartupTask {
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
performStartupTasks();
}
private void performStartupTasks() {
// ...
}
}示例2:啟動時加載初始數據
@Component
public class DataLoader {
private final DictRepository dictRepository ;
public static final List<Dict> CACHE = new ArrayList<>() ;
@EventListener(ApplicationReadyEvent.class)
public void loadData() {
// 加載數據
CACHE.addAll(this.dictRepositry.findAll()) ;
}
}2.2 什么是@PreDestroy
在 Spring Boot 中,@PreDestroy 用于標記一個方法,該方法應在 Bean(一個對象)從 Spring 容器中移除之前運行,通常是在應用程序關閉或 Bean 不再需要時觸發。
你可以將其視為一個清理方法,用于在應用程序完全關閉之前執行必要的任務,比如關閉文件、釋放資源或停止后臺進程。
假設我們有一個服務,它在應用程序啟動時打開一個文件,我們希望在應用程序關閉之前關閉該文件。
@Service
public class FileService {
private BufferedReader bufferedReader;
// 構造函數中打開文件
public FileService() {
try {
this.bufferedReader = new BufferedReader(new FileReader("example.txt"));
System.out.println("File opened successfully.");
} catch (IOException e) {
System.err.println("Failed to open file: " + e.getMessage());
}
}
// 其他方法(如讀取文件內容)...
// 使用 @PreDestroy 注解標記的方法,在 Bean 被銷毀前關閉文件
@PreDestroy
public void cleanupFileResource() {
try {
if (bufferedReader != null) {
bufferedReader.close();
System.out.println("File resource cleaned up successfully.");
}
} catch (IOException e) {
System.err.println("Failed to clean up file resource: " + e.getMessage());
}
}
}對于清理邏輯(@PreDestroy 的替代方案):
如果你想用其他方式替代 @PreDestroy 來執行清理邏輯,你可以實現 DisposableBean 接口,或者使用 @Bean(destroyMethod = "methodName") 的方式。
@Component
public class MyResource implements DisposableBean {
// 初始化邏輯(這里只是示例,通常初始化邏輯可能放在構造函數或其他地方)
public MyResource() {
System.out.println("MyResource initialized.");
}
// 實現 DisposableBean 接口的 destroy 方法,用于清理邏輯
@Override
public void destroy() throws Exception {
System.out.println("Performing cleanup for MyResource...");
// 在這里執行你的清理邏輯
}
}使用 @Bean(destroyMethod = "methodName")
@Configuration
public class AppConfig {
@Bean(destroyMethod = "customCleanup")
public MyResource myResource() {
MyResource resource = new MyResource();
System.out.println("MyResource bean created.");
return resource;
}
// MyResource 類中需要有一個名為 customCleanup 的方法
public static class MyResource {
public void customCleanup() {
System.out.println("Custom cleanup for MyResource...");
// 在這里執行你的清理邏輯
}
}
}































