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

Как уничтожаются бины

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

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

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

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

Уничтожение Bean'ов в Spring Framework

Жизненный цикл Spring bean'а включает несколько этапов, включая создание, инициализацию и уничтожение. Понимание процесса уничтожения критично для правильного управления ресурсами.

Фазы жизненного цикла Bean'а

1. Instantiation      (создание экземпляра)
2. Dependency Injection (внедрение зависимостей)
3. Aware callbacks    (вызовы Aware интерфейсов)
4. Initialization     (инициализация)
5. Ready to use       (готово к использованию)
6. ↓
7. Destruction        (уничтожение) ← МЫ ЗДЕСЬ

Способ 1: @PreDestroy аннотация (РЕКОМЕНДУЕТСЯ)

Это современный и стандартный способ:

import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;

@Component
public class DatabaseConnection {
    
    private Connection connection;
    
    public void connect() {
        System.out.println("Opening database connection...");
        // connection = DriverManager.getConnection(...);
    }
    
    @PreDestroy
    public void closeConnection() {
        System.out.println("Closing database connection...");
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                System.err.println("Error closing connection: " + e.getMessage());
            }
        }
    }
}

Как это работает:

  • Аннотация из пакета jakarta.annotation (или javax.annotation в старых версиях)
  • Spring автоматически вызывает метод помеченный @PreDestroy перед удалением bean'а
  • Гарантируется вызов метода при graceful shutdown приложения

Способ 2: InitializingBean и DisposableBean интерфейсы

Это более старый подход, но всё ещё используется:

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
public class ResourceManager implements InitializingBean, DisposableBean {
    
    private Resource resource;
    
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Initializing resource...");
        resource = new Resource();
        resource.initialize();
    }
    
    @Override
    public void destroy() throws Exception {
        System.out.println("Destroying resource...");
        if (resource != null) {
            resource.cleanup();
        }
    }
}

Недостатки:

  • Связывает код с Spring интерфейсами
  • Менее читаемо чем аннотации

Способ 3: @Bean с initMethod и destroyMethod

Для конфигурации bean'ов через @Bean:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfiguration {
    
    // При использовании класса без Spring интеграции
    @Bean(initMethod = "init", destroyMethod = "cleanup")
    public ThirdPartyService thirdPartyService() {
        return new ThirdPartyService();
    }
}

// Класс из сторонней библиотеки
public class ThirdPartyService {
    
    public void init() {
        System.out.println("Third party service initialized");
        // инициализация
    }
    
    public void cleanup() {
        System.out.println("Third party service cleaned up");
        // очистка ресурсов
    }
}

Используй когда:

  • Работаешь с классами из сторонних библиотек
  • Не можешь изменить исходный класс
  • Нужна гибкость в выборе методов

Способ 4: Scope = Prototype (иногда уничтожение не происходит)

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeBean {
    
    @PreDestroy
    public void destroy() {
        System.out.println("Prototype bean destroyed");
    }
}

ВАЖНО: Для PROTOTYPE scope Spring НЕ отвечает за уничтожение!

  • Spring создает новый экземпляр каждый раз
  • @PreDestroy НЕ гарантируется для prototype beans
  • Ты должен сам управлять очисткой ресурсов
@Component
public class PrototypeConsumer {
    
    private final ObjectFactory<PrototypeBean> prototypeFactory;
    
    public PrototypeConsumer(ObjectFactory<PrototypeBean> prototypeFactory) {
        this.prototypeFactory = prototypeFactory;
    }
    
    public void usePrototype() {
        PrototypeBean bean = prototypeFactory.getObject();
        try {
            // Использование bean'а
        } finally {
            // Ручная очистка (если есть @PreDestroy)
            // или вызов destroy() напрямую
        }
    }
}

Процесс уничтожения контейнера

public class SpringBootApplication {
    
    public static void main(String[] args) {
        // Запуск контейнера
        ApplicationContext context = SpringApplication.run(App.class, args);
        
        // При вызове close() или выходе из main() запускается shutdown hook
        context.close(); // или CtrlC вызывает shutdown hook
        
        // Spring выполняет все @PreDestroy методы в обратном порядке создания
    }
}

// Порядок вызова @PreDestroy
@Component
public class FirstService {
    @PreDestroy
    public void destroy() {
        System.out.println("1. FirstService destroyed");
    }
}

@Component
public class SecondService {
    @PreDestroy
    public void destroy() {
        System.out.println("2. SecondService destroyed");
    }
}

// При shutdown'е выведет:
// 2. SecondService destroyed
// 1. FirstService destroyed
// (в обратном порядке создания, так как SecondService может зависеть от FirstService)

Пример с реальными ресурсами

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import java.io.*;
import org.springframework.stereotype.Component;

@Component
public class FileLogger {
    
    private PrintWriter writer;
    private FileWriter fileWriter;
    
    @PostConstruct
    public void initializeLogger() throws IOException {
        System.out.println("Logger initializing...");
        fileWriter = new FileWriter("application.log", true);
        writer = new PrintWriter(fileWriter, true);
        writer.println("[" + System.currentTimeMillis() + "] Logger started");
    }
    
    public void log(String message) {
        if (writer != null) {
            writer.println("[" + System.currentTimeMillis() + "] " + message);
        }
    }
    
    @PreDestroy
    public void closeLogger() {
        System.out.println("Logger shutting down...");
        try {
            if (writer != null) {
                writer.println("[" + System.currentTimeMillis() + "] Logger stopped");
                writer.close();
            }
            if (fileWriter != null) {
                fileWriter.close();
            }
        } catch (IOException e) {
            System.err.println("Error closing logger: " + e.getMessage());
        }
    }
}

// Использование
@Service
public class ApplicationService {
    
    private final FileLogger logger;
    
    public ApplicationService(FileLogger logger) {
        this.logger = logger;
    }
    
    public void doSomething() {
        logger.log("Doing something important");
    }
}

Graceful Shutdown

Spring поддерживает graceful shutdown - даёт время на завершение запущенных операций:

# application.yml
spring:
  application:
    name: my-app
server:
  shutdown: graceful
  tomcat:
    threads:
      max: 200
      min-spare: 10

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s  # Время ожидания для @PreDestroy
@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {
    
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        System.out.println("Application is shutting down...");
        // Логика очистки перед выходом
    }
}

Сравнение методов

МетодПлюсыМинусыКогда использовать
@PreDestroyСтандартно, чистый кодТребует JSR-250ВСЕГДА - best practice
DisposableBeanЯвная реализацияЗависимость от SpringСтарые проекты
@Bean destroyMethodГибко для 3rd partyСтроковые названия методовВнешние классы
Close listenerДополнительный контрольСложнееСпециальные события

Common Mistakes

// ❌ Плохо: забыли @PreDestroy
public class BadService {
    private Resource resource;
    
    public void cleanup() { // Никогда не вызовется!
        resource.close();
    }
}

// ✅ Хорошо
public class GoodService {
    private Resource resource;
    
    @PreDestroy
    public void cleanup() { // Вызовется автоматически
        resource.close();
    }
}

// ❌ Плохо: exception в @PreDestroy может быть проигнорирована
@PreDestroy
public void cleanup() throws Exception {
    throw new Exception("Something went wrong"); // Проблема!
}

// ✅ Хорошо: обработай исключения
@PreDestroy
public void cleanup() {
    try {
        // очистка
    } catch (Exception e) {
        logger.error("Error during cleanup", e);
    }
}

Резюме

Процесс уничтожения Bean'ов:

  1. Контейнер получает сигнал shutdown'а
  2. Spring вызывает все @PreDestroy методы в обратном порядке создания
  3. Ресурсы освобождаются
  4. Приложение завершается

Используй @PreDestroy — это стандартный, чистый и надёжный способ управления ресурсами при уничтожении bean'ов в Spring.