← Назад к вопросам
Для чего нужна аннотация PreDestroy в Spring?
2.0 Middle🔥 101 комментариев
#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Для чего нужна аннотация @PreDestroy в Spring
@PreDestroy — это аннотация Spring Framework, которая обозначает метод, который должен быть вызван ДО уничтожения (удаления) бина из Spring контекста. Это используется для очистки ресурсов и финализации.
Основная цель @PreDestroy
@PreDestroy вызывает метод перед тем, как Spring удалит бин из контекста. Это позволяет выполнить очистку: закрыть соединения, освободить ресурсы, завершить операции.
Жизненный цикл бина в Spring:
1. Создание (constructor)
2. Инъекция зависимостей
3. @PostConstruct (инициализация)
4. Использование
5. @PreDestroy (очистка) <-- ВОТ ЗДЕСЬ
6. Удаление из контекста
Примеры использования
1. Закрытие соединения с БД
@Component
public class DatabaseConnection {
private Connection connection;
@PostConstruct
public void init() {
try {
// Открыть соединение при создании бина
String url = "jdbc:mysql://localhost:3306/mydb";
this.connection = DriverManager.getConnection(
url, "root", "password"
);
System.out.println("Database connection opened");
} catch (SQLException e) {
e.printStackTrace();
}
}
@PreDestroy
public void cleanup() {
try {
// Закрыть соединение перед уничтожением
if (connection != null && !connection.isClosed()) {
connection.close();
System.out.println("Database connection closed");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public Connection getConnection() {
return connection;
}
}
2. Остановка потоков
@Service
public class BackgroundTaskExecutor {
private ExecutorService executor;
@PostConstruct
public void init() {
// Создать пул потоков
executor = Executors.newFixedThreadPool(5);
System.out.println("Thread pool created");
}
public void submitTask(Runnable task) {
executor.submit(task);
}
@PreDestroy
public void shutdown() {
System.out.println("Shutting down thread pool");
// Остановить приём новых задач
executor.shutdown();
try {
// Ждём завершения текущих задач
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
// Если не завершились за 10 сек, принудительно остановить
executor.shutdownNow();
System.out.println("Thread pool forcibly shut down");
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
System.out.println("Thread pool shutdown complete");
}
}
3. Закрытие файлов и потоков
@Component
public class FileLogger {
private FileWriter fileWriter;
@PostConstruct
public void init() throws IOException {
// Открыть файл логирования
fileWriter = new FileWriter("app.log", true);
System.out.println("File logger initialized");
}
public void log(String message) throws IOException {
fileWriter.write("[" + LocalDateTime.now() + "] " + message + "\n");
fileWriter.flush();
}
@PreDestroy
public void close() throws IOException {
System.out.println("Closing file logger");
if (fileWriter != null) {
fileWriter.flush(); // Выписать оставшиеся данные
fileWriter.close(); // Закрыть файл
System.out.println("File logger closed");
}
}
}
4. Отписка от событий
@Service
public class EventListener implements ApplicationContextAware {
private ApplicationContext context;
private List<String> subscriptions = new ArrayList<>();
@PostConstruct
public void init() {
// Подписаться на события
context.publishEvent(new MyEvent("Listener registered"));
subscriptions.add("event1");
subscriptions.add("event2");
System.out.println("Subscribed to " + subscriptions.size() + " events");
}
@PreDestroy
public void unsubscribe() {
System.out.println("Unsubscribing from events");
for (String subscription : subscriptions) {
System.out.println("Unsubscribed from: " + subscription);
}
subscriptions.clear();
System.out.println("All subscriptions cleared");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.context = applicationContext;
}
}
5. Сохранение состояния
@Component
public class CacheManager {
private Map<String, Object> cache = new ConcurrentHashMap<>();
private static final String CACHE_FILE = "cache.dat";
@PostConstruct
public void init() {
// Загрузить кеш при старте
try {
loadCacheFromFile();
System.out.println("Cache loaded");
} catch (IOException e) {
System.out.println("Cache file not found, starting fresh");
}
}
public void put(String key, Object value) {
cache.put(key, value);
}
public Object get(String key) {
return cache.get(key);
}
@PreDestroy
public void saveCacheState() {
System.out.println("Saving cache state before shutdown");
try {
saveCacheToFile();
System.out.println("Cache saved successfully (" + cache.size() + " entries)");
} catch (IOException e) {
System.err.println("Failed to save cache: " + e.getMessage());
}
}
private void loadCacheFromFile() throws IOException {
// Реализация загрузки
}
private void saveCacheToFile() throws IOException {
// Реализация сохранения
}
}
@PreDestroy vs @PostConstruct
| Аспект | @PostConstruct | @PreDestroy |
|---|---|---|
| Когда вызывается | После создания и инъекции | Перед удалением бина |
| Назначение | Инициализация ресурсов | Очистка ресурсов |
| Примеры | Открыть соединение | Закрыть соединение |
| Кол-во методов | Один на класс | Один на класс |
| Параметры | Без параметров | Без параметров |
@Component
public class ResourceManager {
private Resource resource;
@PostConstruct
public void init() {
// Вызывается СРАЗУ после создания
resource = new Resource();
resource.initialize();
System.out.println("Resource initialized");
}
@PreDestroy
public void cleanup() {
// Вызывается ПЕРЕД удалением
if (resource != null) {
resource.release();
System.out.println("Resource released");
}
}
}
Контекст приложения
// Spring Boot приложение
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context =
SpringApplication.run(Application.class, args);
System.out.println("Application started");
// Использование бина
MyService service = context.getBean(MyService.class);
service.doSomething();
// Закрытие контекста
System.out.println("\nClosing application context...");
context.close(); // <-- ВОТ ЗДЕСЬ ВЫЗЫВАЕТСЯ @PreDestroy
System.out.println("Application stopped");
}
}
@Service
public class MyService {
@PostConstruct
public void init() {
System.out.println("MyService initialized");
}
public void doSomething() {
System.out.println("Doing something...");
}
@PreDestroy
public void cleanup() {
System.out.println("MyService cleanup");
}
}
// Вывод:
// MyService initialized
// Doing something...
//
// Closing application context...
// MyService cleanup
// Application stopped
Real-world пример: REST клиент
@Component
public class HttpClientService {
private CloseableHttpClient httpClient;
@PostConstruct
public void init() {
// Инициализировать HTTP клиент
this.httpClient = HttpClients.createDefault();
System.out.println("HTTP client created");
}
public String get(String url) throws IOException {
HttpGet request = new HttpGet(url);
try (CloseableHttpResponse response = httpClient.execute(request)) {
return EntityUtils.toString(response.getEntity());
}
}
@PreDestroy
public void closeHttpClient() {
System.out.println("Closing HTTP client");
if (httpClient != null) {
try {
httpClient.close();
System.out.println("HTTP client closed");
} catch (IOException e) {
System.err.println("Error closing HTTP client: " + e.getMessage());
}
}
}
}
Best Practices
// 1. Всегда использовать @PreDestroy для очистки ресурсов
@Component
public class GoodPractice {
private Resource resource;
@PostConstruct
public void init() {
resource = new Resource();
}
@PreDestroy
public void cleanup() { // Гарантированно будет вызван
resource.close();
}
}
// 2. Обработка исключений в @PreDestroy
@Component
public class ErrorHandling {
@PreDestroy
public void cleanup() {
try {
// Очистка
closeResources();
} catch (Exception e) {
// Логировать ошибку, но не выбрасывать
System.err.println("Error during cleanup: " + e.getMessage());
}
}
private void closeResources() throws Exception {
}
}
// 3. Не блокировать в @PreDestroy надолго
@Component
public class QuickCleanup {
@PreDestroy
public void cleanup() {
// БЫСТРО закрыть ресурсы
// Не ждать долгих операций
// Spring имеет timeout на выключение
}
}
Когда используется @PreDestroy
1. Завершение приложения (context.close())
2. Перезагрузка контекста
3. Удаление прототипного бина
4. В тестах (после @After)
5. При использовании application.properties: spring.lifecycle.timeout-per-shutdown-phase
Итоговый ответ
@PreDestroy нужна для:
- Очистки ресурсов — закрытие соединений, файлов, потоков
- Корректного завершения — финализация операций
- Сохранения состояния — сохранить данные перед выключением
- Отписки от событий — очистить слушатели
- Предотвращения утечек — освободить память и ресурсы
Это критично для приложений, работающих с БД, файлами, сетевыми соединениями и потоками. Без @PreDestroy приложение может оставить открытые соединения и утечки ресурсов.