← Назад к вопросам
Управляет ли Spring жизненным циклом бина, если используется Prototype
2.8 Senior🔥 121 комментариев
#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Управление жизненным циклом Prototype бинов в Spring
Нет — Spring НЕ управляет полным жизненным циклом Prototype бинов после создания. Это критически важное отличие от Singleton и основной источник ошибок на собеседованиях.
Жизненный цикл Singleton
Spring полностью управляет жизненным циклом Singleton:
1. Создание → instantiation
2. Инъекция зависимостей → injection
3. post-construct → @PostConstruct
4. Использование → active
5. pre-destroy → @PreDestroy
6. Удаление → application shutdown
@Service // Singleton
public class SingletonService {
@PostConstruct
public void init() {
System.out.println("Singleton инициализируется один раз");
}
@PreDestroy
public void cleanup() {
System.out.println("Singleton удаляется при shutdown приложения");
}
}
Spring вызовет init() один раз, а cleanup() при завершении контекста.
Жизненный цикл Prototype
Spring управляет только созданием и инъекцией. Дальше — ответственность разработчика:
1. Создание → instantiation
2. Инъекция зависимостей → injection
3. post-construct → @PostConstruct (Spring ещё вызывает!)
4. Использование → разработчик отвечает
5. pre-destroy → @PreDestroy НЕ вызывается Spring!
6. Удаление → РАЗРАБОТЧИК должен позаботиться
Демонстрация проблемы
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeService {
private static int instanceCount = 0;
private int id;
private Connection dbConnection;
public PrototypeService() {
this.id = ++instanceCount;
System.out.println("Создан Prototype экземпляр #" + id);
}
@PostConstruct
public void init() {
System.out.println("@PostConstruct для экземпляра #" + id);
this.dbConnection = DataSourceManager.getConnection(); // ОТКРЫВАЕМ ресурс
}
@PreDestroy
public void cleanup() {
System.out.println("@PreDestroy для экземпляра #" + id);
if (dbConnection != null) {
dbConnection.close(); // ЗАКРЫВАЕМ ресурс
}
}
public void doWork() {
System.out.println("Работаю в экземпляре #" + id);
}
}
@Service
public class ClientService {
@Autowired
private ObjectFactory<PrototypeService> prototypeFactory;
public void process() {
PrototypeService service1 = prototypeFactory.getObject();
service1.doWork(); // @PostConstruct вызывается
// Что теперь? service1 остаётся в памяти!
// @PreDestroy НЕ будет вызван!
// dbConnection зависнет!
PrototypeService service2 = prototypeFactory.getObject();
service2.doWork();
// Ещё один объект с открытым connection...
}
}
// Вывод:
// Создан Prototype экземпляр #1
// @PostConstruct для экземпляра #1
// Работаю в экземпляре #1
// Создан Prototype экземпляр #2
// @PostConstruct для экземпляра #2
// Работаю в экземпляре #2
// ... приложение завершается ...
// @PreDestroy НИКОГДА не вызывается!
// Memory leak! DB connections зависли!
Когда Spring вызывает @PostConstruct у Prototype?
Всегда, при каждом создании:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class MyPrototype {
@PostConstruct
public void init() {
System.out.println("init вызывается каждый раз");
}
}
// Каждый вызов getObject() → вызов init()
Когда Spring вызывает @PreDestroy у Prototype?
НИКОГДА! Это твоя ответственность:
@Service
public class ProperClientService {
@Autowired
private ObjectFactory<PrototypeService> factory;
public void processWithCleanup() {
PrototypeService service = factory.getObject();
try {
service.doWork();
} finally {
// ТЫ должен вызвать cleanup вручную!
if (service instanceof DisposableBean) {
((DisposableBean) service).destroy(); // ❌ Не очень красиво
}
}
}
}
Правильный подход: DisposableBean
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeService implements DisposableBean {
private Connection dbConnection;
@PostConstruct
public void init() {
this.dbConnection = DataSourceManager.getConnection();
}
@Override
public void destroy() throws Exception {
if (dbConnection != null) {
dbConnection.close();
}
}
}
@Service
public class ClientService {
@Autowired
private ObjectFactory<PrototypeService> factory;
public void process() throws Exception {
PrototypeService service = factory.getObject();
try {
service.doWork();
} finally {
service.destroy(); // ✅ Явно вызваём cleanup
}
}
}
Лучший подход: try-with-resources
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeService implements AutoCloseable {
private Connection dbConnection;
@PostConstruct
public void init() {
this.dbConnection = DataSourceManager.getConnection();
}
@Override
public void close() {
if (dbConnection != null) {
try {
dbConnection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
@Service
public class ClientService {
@Autowired
private ObjectFactory<PrototypeService> factory;
public void process() {
try (PrototypeService service = factory.getObject()) {
service.doWork(); // ✅ Автоматический cleanup
}
}
}
Таблица управления жизненным циклом
| Операция | Singleton | Prototype |
|---|---|---|
| instantiation | Spring | Spring |
| dependency injection | Spring | Spring |
| @PostConstruct | Spring вызывает один раз | Spring вызывает каждый раз |
| @PreDestroy | Spring вызывает | НИКОГДА не вызывает |
| DisposableBean.destroy() | Spring вызывает | ТЫ должен вызвать |
| Memory management | Spring удаляет при shutdown | ТЫ отвечаешь |
Best Practices для Prototype
- Избегай ресурсоёмких операций в Prototype — если есть альтернатива
- Используй AutoCloseable + try-with-resources для гарантированного cleanup
- НЕ полагайся на @PreDestroy для освобождения ресурсов
- Если нужен Singleton с state — переделай архитектуру (ThreadLocal, request-scoped bean)
- Тестируй cleanup — проверь, что ресурсы действительно освобождаются
На собеседовании ожидают
- "Spring управляет только созданием и инъекцией"
- "@PreDestroy не вызывается для Prototype"
- Понимание утечек памяти и resource leaks
- Знание решений (AutoCloseable, DisposableBean)
- Практический пример с DB connection или file handle