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

Управляет ли 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
        }
    }
}

Таблица управления жизненным циклом

ОперацияSingletonPrototype
instantiationSpringSpring
dependency injectionSpringSpring
@PostConstructSpring вызывает один разSpring вызывает каждый раз
@PreDestroySpring вызываетНИКОГДА не вызывает
DisposableBean.destroy()Spring вызываетТЫ должен вызвать
Memory managementSpring удаляет при shutdownТЫ отвечаешь

Best Practices для Prototype

  1. Избегай ресурсоёмких операций в Prototype — если есть альтернатива
  2. Используй AutoCloseable + try-with-resources для гарантированного cleanup
  3. НЕ полагайся на @PreDestroy для освобождения ресурсов
  4. Если нужен Singleton с state — переделай архитектуру (ThreadLocal, request-scoped bean)
  5. Тестируй cleanup — проверь, что ресурсы действительно освобождаются

На собеседовании ожидают

  • "Spring управляет только созданием и инъекцией"
  • "@PreDestroy не вызывается для Prototype"
  • Понимание утечек памяти и resource leaks
  • Знание решений (AutoCloseable, DisposableBean)
  • Практический пример с DB connection или file handle
Управляет ли Spring жизненным циклом бина, если используется Prototype | PrepBro