Бин и 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() { /* очистка при остановке контекста */ }
}

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).