Вопросы о бинах проверяют, понимаете ли вы, что происходит при старте контекста: сколько экземпляров создаётся, когда вызывается @PostConstruct, откуда берутся прокси и почему prototype внутри singleton «не работает».
Скоупы
| Скоуп | Экземпляр | Где |
|---|---|---|
singleton (по умолчанию) | один на контекст | везде |
prototype | новый при каждом запросе бина из контейнера | везде |
request | один на HTTP-запрос | веб |
session | один на HTTP-сессию | веб |
application | один на ServletContext | веб |
websocket | один на WebSocket-сессию | веб |
Singleton в Spring это «один на контекст», а не паттерн Singleton из GoF: можно создать два бина одного класса с разными именами, и это будут два объекта.
Singleton-бин используется всеми потоками сразу, поэтому он должен быть без изменяемого состояния (stateless) или потокобезопасным. Поле-счётчик или «текущий пользователь» в поле сервиса это гонка.
Prototype внутри singleton
@Component @Scope("prototype")
class ReportBuilder { ... }
@Service
class ReportService {
private final ReportBuilder builder; // внедрится ОДИН раз
ReportService(ReportBuilder builder) { this.builder = builder; }
}
Зависимость внедряется один раз при создании singleton, поэтому «новый экземпляр каждый раз» не получится. Решения: ObjectProvider<ReportBuilder> и getObject() на каждый вызов, метод с @Lookup, или scoped proxy (@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)). Для request и session бинов в singleton Spring требует scoped proxy, он сам по себе ищет бин текущего запроса.
Ещё одна особенность prototype: Spring не управляет его уничтожением, @PreDestroy у него не вызывается.
Жизненный цикл бина
- Создание экземпляра (конструктор, внедрение через конструктор).
- Внедрение зависимостей в поля и сеттеры.
*Awareколбэки (BeanNameAware,ApplicationContextAware).BeanPostProcessor.postProcessBeforeInitialization.- Инициализация:
@PostConstruct, затемInitializingBean.afterPropertiesSet(), затемinitMethodиз@Bean. BeanPostProcessor.postProcessAfterInitialization: здесь создаются прокси для@Transactional,@Async,@Cacheable, AOP.- Бин готов к работе.
- При закрытии контекста:
@PreDestroy,DisposableBean.destroy(),destroyMethod.
Важное следствие: в конструкторе и в @PostConstruct вы работаете с самим объектом, а не с прокси. Вызов @Transactional-метода из @PostConstruct через this транзакцию не откроет. Для логики «после полного старта» есть ApplicationReadyEvent или SmartInitializingSingleton.
@PostConstruct и @PreDestroy теперь в пакете jakarta.annotation (Spring 6 и Boot 3 перешли с javax на jakarta).
BeanPostProcessor и BeanFactoryPostProcessor
BeanFactoryPostProcessorработает до создания бинов и меняет их определения (BeanDefinition). Пример: подстановка${...}из свойств (PropertySourcesPlaceholderConfigurer).BeanPostProcessorработает с готовыми экземплярами, может обернуть бин в прокси. Пример:AutowiredAnnotationBeanPostProcessor, создатель AOP-прокси.
Ленивая инициализация
Синглтоны создаются при старте, ошибки видны сразу. @Lazy на бине откладывает создание до первого обращения. Глобально spring.main.lazy-initialization=true ускоряет старт, но переносит ошибки конфигурации на первый запрос.
Частые ошибки
- Изменяемое состояние в singleton-сервисе.
- Ожидание нового prototype при каждом вызове метода singleton.
- Вызов транзакционного или асинхронного метода из
@PostConstruct. - Тяжёлая работа в конструкторе (сетевые вызовы), из-за которой контекст стартует минуты.
Что спросят на собесе
- Какие скоупы бинов есть, какой по умолчанию?
- Потокобезопасен ли singleton-бин?
- Что будет, если внедрить prototype в singleton, и как получить новый экземпляр?
- Расскажите жизненный цикл бина. Где создаются прокси?
- Чем BeanPostProcessor отличается от BeanFactoryPostProcessor?