Получить доступ

Бины: жизненный цикл и scopes

Бин и BeanDefinition, полный жизненный цикл, роль BeanPostProcessor, области видимости и их ловушки.

Бин и BeanDefinition

Бин — объект, созданный и управляемый Spring-контейнером: контейнер решает, когда его создать, как связать зависимости и когда уничтожить.

Важная деталь: сначала контейнер хранит не сам объект, а BeanDefinition — «рецепт» бина (класс, scope, зависимости, init/destroy-методы, lazy-флаг). Бины создаются из этих определений. Поэтому конфигурацию можно менять до создания объектов (см. BeanFactoryPostProcessor).

Полный жизненный цикл

Стадии создания singleton-бина: инстанцирование (конструктор) → внедрение зависимостей (populate) → Aware-колбэки (BeanNameAware, ApplicationContextAware) → BeanPostProcessor.postProcessBeforeInitialization → инициализация (@PostConstruct → InitializingBean.afterPropertiesSet → init-method) → BeanPostProcessor.postProcessAfterInitialization → бин готов. При остановке контекста: @PreDestroy → DisposableBean.destroy → destroy-method.

Знать порядок важно: именно на стадии postProcessAfterInitialization бин при необходимости оборачивается в прокси.

@Component
class Cache {
    @PostConstruct void init()  { /* прогрев после внедрения зависимостей */ }
    @PreDestroy  void close() { /* очистка при остановке контекста */ }
}
Фазы жизненного цикла Spring-бина: создание, внедрение зависимостей, Aware-интерфейсы, BeanPostProcessor до, инициализация, BeanPostProcessor после, бин готов, уничтожение.
Жизненный цикл бина: от создания и внедрения зависимостей через инициализацию до готовности и уничтожения.

BeanPostProcessor — где живёт «магия»

BeanPostProcessor (BPP) — точка расширения, которая вмешивается до и после инициализации КАЖДОГО бина. Именно BPP оборачивают бины в прокси: так работают AOP, @Transactional, @Async, @Cacheable.

Это объясняет «магию» Spring: вы получаете не свой объект, а его прокси-обёртку, добавляющую поведение. Отсюда же типичная ловушка — self-invocation: вызов своего @Transactional-метода через this идёт мимо прокси и не открывает транзакцию (подробнее — в уроке про транзакции/AOP).

Scopes

Scope определяет, сколько живёт и сколько экземпляров у бина. singleton (по умолчанию) — один на контейнер, создаётся жадно при старте (fail-fast). prototype — новый объект на каждый запрос из контейнера/инъекцию. Веб-scopes: request, session, application, websocket — привязаны к жизни запроса/сессии.

singleton ≠ паттерн Singleton из GoF: это «один на контейнер», а не «один на JVM».

Ловушки scope

Для prototype-бинов контейнер НЕ вызывает destroy-колбэк — за освобождение ресурсов отвечаете вы.

Более коварная ловушка — prototype внутри singleton: prototype-зависимость внедрится в singleton ОДИН раз при его создании, и дальше будет один и тот же экземпляр, а не новый при каждом обращении. Чтобы получать новый, инъектируют ObjectProvider<T> (provider.getObject()), используют @Lookup-метод или scoped proxy.

Как отвечать на собеседовании

Объясните роль BeanDefinition (рецепт до объекта), назовите порядок жизненного цикла (инстанцирование → DI → Aware → BPP.before → init → BPP.after → готов; destroy в конце) и подчеркните, что прокси (AOP/@Transactional) навешивает BeanPostProcessor — отсюда self-invocation. Перечислите scopes (singleton≠GoF Singleton, prototype без destroy) и ловушку prototype-в-singleton (ObjectProvider/@Lookup).

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем