← Назад к вопросам
Почему не вызывается метод destroy у Bean со scope="prototype" в Spring?
2.0 Middle🔥 191 комментариев
#Spring Boot и Spring Data#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Почему не вызывается destroy() у Bean со scope="prototype"?
Это фундаментальное различие в управлении жизненным циклом Bean'ов между разными scopes в Spring. Причина кроется в разных моделях владения и ответственности за управление ресурсами.
Главная причина: ответственность за управление жизненным циклом
Singleton (по умолчанию):
@Bean
public UserService userService() { // scope = singleton
return new UserService();
}
Spring контейнер:
- Создаёт Bean один раз
- Управляет его жизненным циклом полностью
- Вызывает @PostConstruct, init-method
- Вызывает @PreDestroy, destroy-method при shutdown контейнера
public class UserService {
@PostConstruct
public void init() {
System.out.println("Init singleton");
}
@PreDestroy
public void cleanup() {
System.out.println("Cleanup singleton"); // ВЫЗОВЕТСЯ
}
}
Prototype:
@Bean
@Scope("prototype")
public UserService userService() { // scope = prototype
return new UserService();
}
Spring контейнер:
- Создаёт новый Bean каждый раз при запросе
- Управляет жизненным циклом только до момента возврата
- Вызывает @PostConstruct, init-method
- НЕ вызывает @PreDestroy, destroy-method
Почему именно так?
1. Ответственность переходит клиенту
Когда вы просите prototype Bean:
@Service
public class OrderService {
@Autowired
private UserService userService; // Получает новый экземпляр
public void processOrder() {
// userService используется
}
// Spring не знает, когда вы закончили с userService!
// OrderService отвечает за cleanup
}
Проблема: Spring не может знать, когда клиент закончил использовать Bean.
2. Масштабируемость
Если бы Spring отслеживал все prototype Bean'ы:
// Если каждый запрос создаёт prototype Bean
for (int i = 0; i < 1_000_000; i++) {
UserService service = context.getBean(UserService.class);
// Spring должен запомнить все 1 млн экземпляров
// Вызвать destroy() для каждого
// Невозможно масштабировать!
}
3. Нет единого контейнера
Прототип может быть:
- Передан в другой класс
- Сохранён в переменную
- Затеян в другой Spring контекст
public class UserService {
@Autowired
private UserRepository repo; // Singleton
@Autowired
private Logger logger; // Prototype
public void save(User user) {
// Какой код отвечает за cleanup Logger?
repo.save(user);
logger.log("User saved");
// logger.destroy() — кто это вызовет?
}
}
Решение 1: ObjectFactory для управления
@Service
public class OrderService {
@Autowired
private ObjectFactory<UserService> userServiceFactory;
public void processOrder() {
UserService service = userServiceFactory.getObject();
try {
// Используем service
} finally {
// Если UserService реализует DisposableBean
if (service instanceof DisposableBean) {
((DisposableBean) service).destroy(); // Вызываем вручную
}
}
}
}
Решение 2: Реализовать DisposableBean
@Component
@Scope("prototype")
public class UserService implements DisposableBean {
private Connection connection;
@PostConstruct
public void init() {
this.connection = createConnection();
System.out.println("Connection opened");
}
@Override
public void destroy() throws Exception {
if (connection != null) {
connection.close(); // Вызовите cleanup вручную
System.out.println("Connection closed");
}
}
private Connection createConnection() {
// ...
return null;
}
}
Решение 3: Request Scope (если используешь Web)
@Bean
@Scope("request")
public UserService userService() {
return new UserService();
}
Request scope:
- Создаётся один раз на HTTP request
- Destroy() ВЫЗОВЕТСЯ при завершении request
- Идеально для web приложений
Сравнение Scopes
| Scope | Когда destroy() | Использование |
|---|---|---|
| singleton | При shutdown контейнера | ВЫЗОВЕТСЯ |
| prototype | Никогда | НЕ ВЫЗОВЕТСЯ |
| request | При завершении request | ВЫЗОВЕТСЯ |
| session | При завершении session | ВЫЗОВЕТСЯ |
| application | При shutdown | ВЫЗОВЕТСЯ |
Практический пример: Connection Pool
public class DatabaseService {
private Connection connection;
@PostConstruct
public void init() {
this.connection = DriverManager.getConnection("jdbc:mysql://localhost/db");
System.out.println("Connection opened");
}
@PreDestroy
public void cleanup() {
if (connection != null) {
try {
connection.close();
System.out.println("Connection closed");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
// НЕПРАВИЛЬНО: prototype scope
@Configuration
public class WrongConfig {
@Bean
@Scope("prototype") // Connection утечка!
public DatabaseService dbService() {
return new DatabaseService();
}
}
// ПРАВИЛЬНО: singleton scope
@Configuration
public class RightConfig {
@Bean
// @Scope("singleton") — по умолчанию
public DatabaseService dbService() {
return new DatabaseService();
// cleanup() вызовется при shutdown
}
}
Когда использовать Prototype?
Используй prototype когда:
- Bean содержит mutable state, специфичный для клиента
- Bean имеет сложное состояние, зависящее от контекста
- Клиент полностью отвечает за управление жизненным циклом
НЕ используй prototype когда:
- Bean имеет ресурсы (connections, streams, threads)
- Нужен automatic cleanup
- Все экземпляры используют одни и те же зависимости
Итоговый вывод
destroy() не вызывается для prototype Bean'ов потому что:
- Spring не владеет жизненным циклом — ответственность передана клиенту
- Масштабируемость — невозможно отслеживать миллионы экземпляров
- Прозрачность — Bean может быть передан куда угодно
- Философия Prototype — максимальная гибкость, но минимум управления
Это главное различие между singleton (Spring управляет) и prototype (клиент управляет)!