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

Как изменить бин перед уничтожением

1.8 Middle🔥 91 комментариев
#Spring Boot и Spring Data#Spring Framework

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

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

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

# Изменение бина перед уничтожением в Spring

Введение

В Spring Framework жизненный цикл бина включает несколько фаз: создание, инициализация, использование и наконец уничтожение. Перед уничтожением бина часто требуется выполнить какую-то логику: освободить ресурсы, сохранить состояние, закрыть соединения и т.д. Spring предоставляет несколько механизмов для выполнения такой логики.

Способы изменения бина перед уничтожением

1. Метод с аннотацией @PreDestroy

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

import javax.annotation.PreDestroy;

@Component
public class DatabaseConnection {
    private Connection connection;
    
    public void init() {
        System.out.println("Initializing connection");
        // Инициализация соединения
    }
    
    @PreDestroy
    public void cleanup() {
        System.out.println("Cleaning up before destroy");
        // Изменяем состояние перед уничтожением
        if (connection != null) {
            try {
                connection.close();
                System.out.println("Connection closed");
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

2. Интерфейс DisposableBean

Имплементирование интерфейса DisposableBean из Spring:

import org.springframework.beans.factory.DisposableBean;

@Component
public class FileProcessor implements DisposableBean {
    private FileWriter fileWriter;
    
    public FileProcessor() throws IOException {
        this.fileWriter = new FileWriter("output.txt");
    }
    
    @Override
    public void destroy() throws Exception {
        System.out.println("Calling destroy method");
        // Изменяем состояние: закрываем файл
        if (fileWriter != null) {
            fileWriter.flush();
            fileWriter.close();
            System.out.println("FileWriter closed");
        }
    }
}

3. Параметр destroyMethod в @Bean

Для конфигурационного класса можно указать метод, вызываемый при уничтожении:

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

@Configuration
public class AppConfig {
    
    @Bean(destroyMethod = "cleanup")
    public DataSource dataSource() {
        DataSource ds = new DataSource();
        ds.setUrl("jdbc:mysql://localhost:3306/mydb");
        return ds;
    }
}

public class DataSource {
    private String url;
    
    public void setUrl(String url) {
        this.url = url;
    }
    
    public void cleanup() {
        System.out.println("DataSource cleanup: " + url);
        // Логика очистки
    }
}

4. Параметр destroyMethod="" для отключения

@Bean(destroyMethod = "")
public SomeService someService() {
    // Без вызова метода destroy
    return new SomeService();
}

5. Интерфейс InitializingBean и DisposableBean вместе

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

@Component
public class CompleteLifecycleBean implements InitializingBean, DisposableBean {
    private String resource;
    
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Initializing bean");
        this.resource = "initialized";
    }
    
    @Override
    public void destroy() throws Exception {
        System.out.println("Destroying bean");
        // Меняем состояние перед уничтожением
        this.resource = null;
        System.out.println("Resource released");
    }
}

6. Использование BeanPostProcessor для глобального управления

import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
    
    @Override
    public Object postProcessBeforeInitialization(
            Object bean, String beanName) {
        System.out.println("Before init: " + beanName);
        return bean;
    }
    
    @Override
    public Object postProcessAfterInitialization(
            Object bean, String beanName) {
        System.out.println("After init: " + beanName);
        return bean;
    }
}

7. Практический пример: Database Pool

import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.List;

@Component
public class DatabasePool {
    private List<Connection> connections = new ArrayList<>();
    private static final int POOL_SIZE = 5;
    private volatile boolean shutdown = false;
    
    @PostConstruct
    public void init() throws Exception {
        System.out.println("Creating database pool");
        for (int i = 0; i < POOL_SIZE; i++) {
            Connection conn = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/mydb", "user", "password");
            connections.add(conn);
        }
    }
    
    @PreDestroy
    public void cleanup() {
        System.out.println("Shutting down database pool");
        shutdown = true;
        
        // Изменяем состояние перед уничтожением
        for (Connection conn : connections) {
            try {
                if (!conn.isClosed()) {
                    conn.close();
                    System.out.println("Connection closed");
                }
            } catch (Exception e) {
                System.err.println("Error closing connection: " + e.getMessage());
            }
        }
        connections.clear();
    }
    
    public Connection getConnection() {
        if (shutdown) {
            throw new IllegalStateException("Pool is shutdown");
        }
        return connections.isEmpty() ? null : connections.get(0);
    }
}

Порядок вызова методов жизненного цикла

1. Constructor
2. @Autowired (setter injection)
3. @PostConstruct / InitializingBean.afterPropertiesSet()
4. Использование бина
5. @PreDestroy / DisposableBean.destroy()
6. Уничтожение бина

Важные моменты

  1. @PreDestroy вызывается только при нормальном завершении приложения
  2. Exception в destroy методе должна быть обработана, иначе может помешать выключению
  3. Порядок вызова для нескольких @PreDestroy методов не гарантирован
  4. Thread-safe методы уничтожения критичны для многопоточных приложений
  5. Не рекомендуется создавать новые объекты в destroy методе

Заключение

Для изменения бина перед уничтожением рекомендуется использовать @PreDestroy аннотацию как самый гибкий и современный подход. Он позволяет выполнить необходимую логику очистки и гарантирует, что ресурсы будут освобождены перед завершением приложения.