Комментарии (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'ов:
- Контейнер получает сигнал shutdown'а
- Spring вызывает все @PreDestroy методы в обратном порядке создания
- Ресурсы освобождаются
- Приложение завершается
Используй @PreDestroy — это стандартный, чистый и надёжный способ управления ресурсами при уничтожении bean'ов в Spring.