← Назад к вопросам

Зачем нужен @PreDestroy?

2.2 Middle🔥 61 комментариев
#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ

@PreDestroy — это аннотация из JSR-250 (Java annotation для управления жизненным циклом), которая используется в Spring для обозначения метода, который должен быть вызван перед тем, как бин будет удалён из контекста приложения и сборен сборщиком мусора.

Основное назначение

@PreDestroy выполняет функцию очистки ресурсов (cleanup) и graceful shutdown бина. Это критично для:

  • Закрытия соединений с БД
  • Освобождения сокетов и сетевых ресурсов
  • Сохранения состояния перед завершением
  • Отписки от событий/слушателей
  • Корректного завершения фоновых потоков

Жизненный цикл бина со @PreDestroy

Полный цикл выглядит так:

  1. @PostConstruct → инициализация бина (вызывается после внедрения зависимостей)
  2. Бин работает в контексте приложения
  3. @PreDestroy → очистка перед удалением
  4. Бин удаляется из памяти

Пример использования

import javax.annotation.PreDestroy;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Component
public class DatabaseConnectionPool {
    private ExecutorService threadPool;
    private Connection connection;
    
    @PostConstruct
    public void init() {
        // Инициализация при создании бина
        threadPool = Executors.newFixedThreadPool(10);
        connection = createDatabaseConnection();
        System.out.println("Connection pool initialized");
    }
    
    @PreDestroy
    public void cleanup() {
        // Очистка перед удалением
        System.out.println("Cleaning up resources...");
        
        if (threadPool != null && !threadPool.isShutdown()) {
            threadPool.shutdown();
            try {
                threadPool.awaitTermination(5, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                threadPool.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }
        
        if (connection != null) {
            try {
                connection.close();
                System.out.println("Database connection closed");
            } catch (SQLException e) {
                System.err.println("Error closing connection: " + e.getMessage());
            }
        }
    }
    
    private Connection createDatabaseConnection() {
        // Создание подключения
        return null; // для примера
    }
}

Практический пример с REST сервисом

@Component
public class HttpClientManager {
    private HttpClient httpClient;
    private ScheduledExecutorService scheduler;
    
    @PostConstruct
    public void initialize() {
        httpClient = HttpClient.newHttpClient();
        scheduler = Executors.newScheduledThreadPool(5);
        
        // Запуск периодической очистки кэша
        scheduler.scheduleAtFixedRate(
            this::cleanCache,
            0, 1, TimeUnit.MINUTES
        );
        
        System.out.println("HTTP client initialized");
    }
    
    @PreDestroy
    public void shutdown() {
        System.out.println("Shutting down HTTP client...");
        
        // Остановка scheduler
        scheduler.shutdownNow();
        
        // Закрытие HTTP клиента
        if (httpClient != null) {
            try {
                // Некоторые реализации HttpClient требуют явного закрытия
                System.out.println("HTTP client closed");
            } catch (Exception e) {
                System.err.println("Error during shutdown: " + e.getMessage());
            }
        }
    }
    
    private void cleanCache() {
        System.out.println("Cache cleaned");
    }
}

Важные аспекты

Когда вызывается:

  • При остановке Spring контекста (shutdown)
  • При удалении singleton бина
  • При закрытии ApplicationContext

Ограничения и особенности:

  • Метод не должен иметь параметров
  • Метод должен быть public или package-private
  • В scope=prototype бины @PreDestroy может не вызваться (Spring не отслеживает их удаление)
  • Исключения в @PreDestroy могут помешать полной очистке других бинов

Best practices:

  1. Всегда обрабатывай исключения в @PreDestroy
  2. Убедись, что cleanup идемпотентен (можно вызвать дважды безопасно)
  3. Логируй что и когда закрывается для дебага утечек ресурсов
  4. Используй try-finally или try-with-resources где возможно
  5. Не полагайся на порядок вызова @PreDestroy для разных бинов

Альтернативы

  • destroyMethod в @Bean аннотации
  • Реализация DisposableBean интерфейса (устаревший подход)
  • @EventListener(ApplicationEvent.ContextClosedEvent.class)

@PreDestroy — это essential инструмент для правильного управления ресурсами и предотвращения утечек памяти в Spring приложениях.

Зачем нужен @PreDestroy? | PrepBro