Как изменить бин перед уничтожением
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Изменение бина перед уничтожением в 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. Уничтожение бина
Важные моменты
- @PreDestroy вызывается только при нормальном завершении приложения
- Exception в destroy методе должна быть обработана, иначе может помешать выключению
- Порядок вызова для нескольких @PreDestroy методов не гарантирован
- Thread-safe методы уничтожения критичны для многопоточных приложений
- Не рекомендуется создавать новые объекты в destroy методе
Заключение
Для изменения бина перед уничтожением рекомендуется использовать @PreDestroy аннотацию как самый гибкий и современный подход. Он позволяет выполнить необходимую логику очистки и гарантирует, что ресурсы будут освобождены перед завершением приложения.