В какие моменты создаются экземпляры сервиса с прототипной областью в Spring?
Что проверяют
Понимаете ли вы, что прототипный бин создаётся по запросу к контейнеру, а не при старте контекста и не на каждый вызов метода. Главный подвох идёт следом: что будет, если внедрить prototype в singleton.
Как отвечать
Бин со @Scope("prototype") (или ConfigurableBeanFactory.SCOPE_PROTOTYPE) не создаётся при поднятии контекста, в отличие от singleton. Новый экземпляр появляется в двух случаях:
- явный запрос: каждый вызов
context.getBean(MyService.class)возвращает новый объект; - внедрение: когда контейнер создаёт бин, которому нужен прототип. Каждая точка внедрения получает свой экземпляр, но ровно один, в момент создания зависимого бина.
Отсюда классическая ловушка: singleton создаётся один раз, значит и прототип в него внедрён один раз. Дальше singleton работает с одним и тем же экземпляром, и scope prototype ничего не даёт. Как получать новый объект на каждое использование:
ObjectProvider<MyService>: внедряете провайдер и зовётеgetObject()там, где нужен свежий экземпляр. Самый частый вариант в современном коде;@Lookup: метод, который Spring переопределяет через CGLIB и возвращает из него новый бин;jakarta.inject.Provider<T>: тот же подход через стандарт JSR-330;- scoped proxy (
proxyMode = ScopedProxyMode.TARGET_CLASS): внедряется прокси, и он создаёт новый экземпляр на каждый вызов метода. Об этом стоит сказать отдельно, обычно ждут не такого поведения.
Жизненный цикл: колбэки инициализации (@PostConstruct, afterPropertiesSet) у прототипа вызываются, а @PreDestroy нет. Контейнер отдаёт объект и больше за ним не следит, освобождать ресурсы должен код, который его получил.
Где спотыкаются
Отвечают «при каждом обращении к бину». Без уточнения про внедрение в singleton это неверно, и именно тут интервьюер задаёт следующий вопрос.
Считают прототип решением для потокобезопасности: «каждый поток получает свой экземпляр». Привязки к потоку нет. Для этого есть SimpleThreadScope, и по умолчанию он не зарегистрирован.
Ждут вызова @PreDestroy у прототипа при закрытии контекста и держат в нём открытые соединения или файлы.