← Назад к вопросам
Можно ли запустить PreDestroy в Prototype?
2.0 Middle🔥 71 комментариев
#Docker, Kubernetes и DevOps#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Можно ли запустить @PreDestroy в Prototype scope
Ответ: Технически можно, но @PreDestroy НЕ БУДЕТ ВЫЗВАН автоматически для Prototype бинов. Вот почему и как это исправить:
1. Различие между Singleton и Prototype scopes
Singleton (по умолчанию)
@Component
@Scope("singleton") // По умолчанию
public class SingletonService {
@PostConstruct
public void init() {
System.out.println("Singleton: init called");
}
@PreDestroy
public void destroy() {
System.out.println("Singleton: destroy called"); // ✓ БУДЕТ ВЫЗВАН
}
}
Prototype (NEW каждый раз)
@Component
@Scope("prototype") // ← Новый объект каждый раз
public class PrototypeService {
@PostConstruct
public void init() {
System.out.println("Prototype: init called"); // ✓ БУДЕТ ВЫЗВАН
}
@PreDestroy
public void destroy() {
System.out.println("Prototype: destroy called"); // ✗ НЕ БУДЕТ ВЫЗВАН!
}
}
2. Почему @PreDestroy не работает с Prototype
Жизненный цикл разных scopes
public class BeanLifecycleComparison {
public void explainDifference() {
/*
SINGLETON (Spring управляет полностью):
┌─────────────────────────────────────────┐
│ Spring ApplicationContext │
│ │
│ 1. create() → new instance │
│ 2. @PostConstruct → вызывается │
│ 3. использование │
│ 4. context.close() │
│ 5. @PreDestroy → вызывается ✓ │
│ 6. очистка ресурсов │
└─────────────────────────────────────────┘
PROTOTYPE (ты управляешь):
┌─────────────────────────────────────────┐
│ Spring ApplicationContext │
│ │
│ 1. getBean() → new instance │
│ 2. @PostConstruct → вызывается │
│ 3. вот тебе инстанс (дальше ты отвечаешь)
│ │
│ Spring НЕ знает когда ты закончишь │
│ Spring НЕ вызовет @PreDestroy ✗ │
│ ТЫ должен вызвать destroy() сам │
└─────────────────────────────────────────┘
*/
}
}
3. Демонстрация проблемы
Пример: @PreDestroy НЕ вызывается
@Component
@Scope("prototype")
public class DatabaseConnection {
private Connection connection;
@PostConstruct
public void init() {
System.out.println("Establishing DB connection...");
// connection = DriverManager.getConnection(...);
}
@PreDestroy
public void closeConnection() {
System.out.println("Closing DB connection"); // ✗ Никогда не выведется!
// if (connection != null) connection.close();
}
public void query(String sql) {
System.out.println("Executing: " + sql);
}
}
// Использование
@Service
public class MyService {
@Autowired
private ApplicationContext context;
public void doSomething() {
DatabaseConnection conn = context.getBean(DatabaseConnection.class);
// Output: "Establishing DB connection..."
conn.query("SELECT * FROM users");
// Когда закончим, соединение остается открытым!
// @PreDestroy никогда не вызывается
// Утечка ресурсов!
}
}
4. Решения
Решение 1: Вызвать destroy() вручную
@Component
@Scope("prototype")
public class PrototypeWithManualDestroy {
@PostConstruct
public void init() {
System.out.println("Initialized");
}
@PreDestroy
public void destroy() {
System.out.println("Destroyed"); // ← Вызовется, если вызвать вручную
}
// Альтернативный метод для явного вызова
public void cleanup() {
destroy();
}
}
@Service
public class Service1 {
@Autowired
private ObjectFactory<PrototypeWithManualDestroy> factory;
public void doSomething() {
PrototypeWithManualDestroy proto = factory.getObject();
// Output: "Initialized"
// ... использование ...
// Вручную вызываем cleanup
proto.cleanup(); // Вызовет @PreDestroy
// Output: "Destroyed" ✓
}
}
Решение 2: Использовать DestructionAwareBeanPostProcessor
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
@Component
public class PrototypeDestructionProcessor implements DestructionAwareBeanPostProcessor {
@Override
public void postProcessBeforeDestruction(Object bean, String beanName) {
// Вызывается для ВСЕХ бинов перед их уничтожением
if (bean instanceof PrototypeService) {
System.out.println("Destroying prototype bean: " + beanName);
// Здесь можно вызвать cleanup логику
}
}
}
// Использование
@Component
@Scope("prototype")
public class PrototypeService implements DestructionAwareBeanPostProcessor {
@PostConstruct
public void init() {
System.out.println("Init");
}
@PreDestroy
public void destroy() {
System.out.println("Destroy"); // Будет вызван при правильной обработке
}
}
Решение 3: Использовать ObjectFactory с явным управлением
@Service
public class ProtoypeManagementService {
@Autowired
private ObjectFactory<PrototypeBean> factory;
private List<PrototypeBean> createdBeans = new ArrayList<>();
public PrototypeBean createAndTrack() {
PrototypeBean bean = factory.getObject();
createdBeans.add(bean); // ← Отслеживаем созданные бины
return bean;
}
// При завершении
@PreDestroy
public void cleanupAllBeans() {
for (PrototypeBean bean : createdBeans) {
bean.destroy(); // Вызываем destroy для каждого
}
}
}
Решение 4: Использовать Scope с настраиваемым режимом
import org.springframework.beans.factory.config.Scope;
@Configuration
public class CustomScopeConfig {
@Bean
public static CustomScope customScope() {
return new CustomScope();
}
}
public class CustomScope implements Scope {
private final Map<String, Object> objects = new HashMap<>();
private final Map<String, Runnable> destructionCallbacks = new HashMap<>();
@Override
public Object get(String name, ObjectFactory<?> factory) {
return objects.computeIfAbsent(name, k -> factory.getObject());
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
destructionCallbacks.put(name, callback);
}
public void close() {
// Явно вызываем все callbacks при закрытии
destructionCallbacks.values().forEach(Runnable::run);
objects.clear();
}
}
5. Best Practice: Когда использовать Prototype
Проблема
// ✗ ПЛОХО: Создавать prototype для каждого запроса
@Component
@Scope("prototype")
public class RequestService {
@PreDestroy
public void cleanup() {
// Никогда не вызовется автоматически
}
}
@RestController
public class MyController {
@Autowired
private ApplicationContext context;
@GetMapping("/api/data")
public ResponseEntity<?> getData() {
RequestService service = context.getBean(RequestService.class);
// ✗ Утечка: cleanup() никогда не вызовется
}
}
Решение: Request scope вместо Prototype
// ✓ ХОРОШО: Использовать Request scope
@Component
@Scope("request")
public class RequestService {
@PreDestroy
public void cleanup() {
// ✓ БУДЕТ ВЫЗВАН в конце request lifecycle
}
}
@RestController
public class MyController {
@Autowired
private RequestService service; // ✓ Spring будет управлять
@GetMapping("/api/data")
public ResponseEntity<?> getData() {
// service автоматически cleanup'ится
}
}
6. Таблица: Жизненные циклы разных scopes
| Scope | @PostConstruct | @PreDestroy | Управление | Когда использовать |
|---|---|---|---|---|
| singleton | ✓ | ✓ | Spring | Сервисы, repositories, конфиги |
| prototype | ✓ | ✗* | Ты | Stateful объекты, неустойчивые к concurrent access |
| request | ✓ | ✓ | Spring | Логика, специфичная для request |
| session | ✓ | ✓ | Spring | Данные пользователя (в web app) |
| websocket | ✓ | ✓ | Spring | WebSocket endpoints |
*Для Prototype нужно вызывать @PreDestroy вручную или использовать ObjectFactory
7. Рекомендация: Singleton vs Prototype
Когда использовать SINGLETON
@Component // ← Singleton по умолчанию
public class UserRepository {
// Stateless, thread-safe
// @PreDestroy будет вызван автоматически
}
@Component
public class EmailService {
// Stateless
// @PreDestroy будет вызван автоматически
}
Когда использовать PROTOTYPE (редко)
@Component
@Scope("prototype")
public class UserRequest {
// Содержит данные конкретного request
// Нельзя шарить между потоками
// @PreDestroy потребует специальной обработки
}
// Лучше вариант:
@Component
@Scope("request") // ← Используй request scope вместо prototype
public class UserRequest {
// @PreDestroy будет вызван автоматически
}
8. Полный пример: Правильное управление ресурсами
// BAD: Prototype с утечкой ресурсов
@Component
@Scope("prototype")
public class BadDatabaseConnection {
private Connection connection;
@PostConstruct
public void init() {
// connection = createConnection();
}
@PreDestroy
public void destroy() {
// Никогда не будет вызван!
}
}
// GOOD: Singleton с управлением циклом жизни
@Component
public class GoodDatabaseConnection {
private final ConnectionPool pool;
@PostConstruct
public void init() {
pool.initialize();
}
@PreDestroy
public void destroy() {
pool.close(); // ✓ Будет вызван при shutdown
}
public Connection getConnection() {
return pool.acquire();
}
}
// GOOD ALTERNATIVE: Request scope для request-specific логики
@Component
@Scope("request")
public class RequestContext {
private String requestId;
@PostConstruct
public void init() {
this.requestId = UUID.randomUUID().toString();
}
@PreDestroy
public void cleanup() {
// ✓ Вызовется в конце request
}
}
Итоговый ответ
Можно добавить @PreDestroy к Prototype бину, но он НЕ БУДЕТ ВЫЗВАН автоматически!
Почему:
Singleton = Spring управляет полным жизненным циклом
→ @PreDestroy вызывается при context.close()
Prototype = Spring создает объект, но ты им управляешь
→ Spring НЕ знает когда ты закончишь
→ @PreDestroy должен вызвать ТЫ
Решения:
-
Вызвать destroy() вручную
proto.destroy(); // Вызовет @PreDestroy -
Использовать ObjectFactory для отслеживания
ObjectFactory<Proto> factory; Proto proto = factory.getObject(); -
Использовать Request/Session scope вместо Prototype
@Scope("request") // @PreDestroy вызовется автоматически -
Использовать DestructionAwareBeanPostProcessor
ВАЖНОЕ ПРАВИЛО:
Производное правило:
- Используй Singleton для stateless компонентов (default)
- Используй @Scope("request") для request-specific логики
- Избегай Prototype, если не понимаешь жизненный цикл