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

Для чего нужна аннотация 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 нужна для:

  1. Очистки ресурсов — закрытие соединений, файлов, потоков
  2. Корректного завершения — финализация операций
  3. Сохранения состояния — сохранить данные перед выключением
  4. Отписки от событий — очистить слушатели
  5. Предотвращения утечек — освободить память и ресурсы

Это критично для приложений, работающих с БД, файлами, сетевыми соединениями и потоками. Без @PreDestroy приложение может оставить открытые соединения и утечки ресурсов.