Какие этапы жизненного цикла Bean
Жизненный цикл бина в Spring — это строгая последовательность шагов, через которые проходит объект с момента сканирования его определения до уничтожения при выключении приложения.
🟢 Junior Level
Жизненный цикл бина в Spring — это строгая последовательность шагов, через которые проходит объект с момента сканирования его определения до уничтожения при выключении приложения.
5 основных шагов (упрощенная модель)
1. Instantiation (Конструктор) → Создание объекта в памяти
↓
2. Populate Properties (@Autowired) → Внедрение зависимостей
↓
3. Initialization (@PostConstruct) → Настройка и проверка готовности
↓
4. Ready for use → Обслуживание бизнес-логики приложения
↓
5. Destruction (@PreDestroy) → Освобождение ресурсов при остановке контекста
Базовый пример с демонстрацией порядка вызовов
package com.example.demo;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class NotificationManager {
private TemplateRepository templateRepository;
// Шаг 1: Конструктор (Instantiation)
public NotificationManager() {
System.out.println("1. Конструктор вызван. templateRepository еще null: "
+ (templateRepository == null));
}
// Шаг 2: Внедрение зависимостей (Populate Properties)
@Autowired
public void setTemplateRepository(TemplateRepository templateRepository) {
this.templateRepository = templateRepository;
System.out.println("2. Зависимости внедрены через setter");
}
// Шаг 3: Инициализация (Initialization)
@PostConstruct
public void init() {
System.out.println("3. @PostConstruct: все зависимости на месте, загружаем шаблоны в кэш");
}
// Шаг 4: Бизнес-метод (Ready for use)
public void sendNotification(String message) {
System.out.println("4. Отправка уведомления: " + message);
}
// Шаг 5: Уничтожение (Destruction)
@PreDestroy
public void cleanup() {
System.out.println("5. @PreDestroy: закрытие пула сетевых соединений");
}
}
🟡 Middle Level
Внутри контейнера Spring процесс разбивается на 12 канонических шагов. Знание этой последовательности позволяет точно управлять созданием компонентов и избегать скрытых дефектов.
Канонический порядок: 12 фаз создания и утилизации бина
[ 1. Instantiation ]
Выбор конструктора и создание экземпляра класса в куче (heap).
↓
[ 2. Populate Properties ]
Инъекция зависимостей в поля и сеттеры (вызов postProcessProperties).
↓
[ 3. BeanNameAware / BeanClassLoaderAware / BeanFactoryAware ]
Контейнер передает бину его имя (ID), ClassLoader и ссылку на BeanFactory.
↓
[ 4. BPP: postProcessBeforeInitialization ]
Вызов коллбэков всеми зарегистрированными BeanPostProcessor.
Здесь CommonAnnotationBeanPostProcessor вызывает методы с @PostConstruct.
Здесь ApplicationContextAwareProcessor передает ApplicationContext.
↓
[ 5. InitializingBean.afterPropertiesSet() ]
Вызов стандартного контрактного метода интерфейса InitializingBean.
↓
[ 6. Custom init-method ]
Вызов кастомного метода, заданного в @Bean(initMethod = "customInit").
↓
[ 7. BPP: postProcessAfterInitialization ]
Пост-процессинг после инициализации.
ЗДЕСЬ создаются AOP Dynamic Proxy (CGLIB / JDK Proxy) для @Transactional, @Async.
↓
[ 8. READY FOR USE ]
Бин зарегистрирован в кэше синглтонов и готов к работе.
↓
[ 9. Context Closes (Начало фазы Destruction) ]
Контейнер инициирует корректное завершение (Graceful Shutdown).
↓
[ 10. @PreDestroy ]
Вызов методов с аннотацией @PreDestroy (через InitDestroyAnnotationBeanPostProcessor).
↓
[ 11. DisposableBean.destroy() ]
Вызов метода интерфейса DisposableBean.
↓
[ 12. Custom destroy-method ]
Вызов метода, объявленного в @Bean(destroyMethod = "customDestroy").
Сравнение трёх механизмов инициализации и уничтожения
Spring предоставляет три конкурирующих способа задать логику инициализации и очистки. Контейнер вызывает их строго друг за другом:
| Порядок | Способ инициализации | Способ уничтожения | Стандарт / Спецификация | Рекомендация |
|---|---|---|---|---|
| 1-й | @PostConstruct |
@PreDestroy |
JSR-250 / Jakarta Annotations | Рекомендуется (чистый код, без привязки к Spring API) |
| 2-й | InitializingBean.afterPropertiesSet() |
DisposableBean.destroy() |
Интерфейсы Spring Framework | Устарело (создает жесткую связь со Spring) |
| 3-й | @Bean(initMethod = "...") |
@Bean(destroyMethod = "...") |
Декларативная конфигурация | Идеально для сторонних библиотек без аннотаций |
package com.example.demo;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class LifecycleDemoBean implements InitializingBean, DisposableBean {
// 1-я инициализация
@PostConstruct
public void postConstruct() {
System.out.println("Шаг 1: @PostConstruct");
}
// 2-я инициализация
@Override
public void afterPropertiesSet() {
System.out.println("Шаг 2: InitializingBean.afterPropertiesSet()");
}
// 3-я инициализация (указывается в @Bean(initMethod = "customInit"))
public void customInit() {
System.out.println("Шаг 3: custom init-method");
}
// 1-е уничтожение
@PreDestroy
public void preDestroy() {
System.out.println("Шаг 10: @PreDestroy");
}
// 2-е уничтожение
@Override
public void destroy() {
System.out.println("Шаг 11: DisposableBean.destroy()");
}
// 3-е уничтожение (указывается в @Bean(destroyMethod = "customDestroy"))
public void customDestroy() {
System.out.println("Шаг 12: custom destroy-method");
}
}
🔴 Senior Level
Внутренний механизм Spring: AbstractAutowireCapableBeanFactory
Посмотрим на этапы через призму низкоуровневого кода Spring (AbstractAutowireCapableBeanFactory.java):
// Упрощенный псевдокод ядра Spring Framework
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 1. Instantiation
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// 2. Метаданные пост-процессинга (MergedBeanDefinitionPostProcessor)
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
// 3. Early Singleton Exposure (публикация фабрики для разрешения циклов)
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences
&& isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 4. Populate Bean (внедрение полей и сеттеров)
populateBean(beanName, mbd, instanceWrapper);
// 5. Initialize Bean (Aware, BPP, Init methods, AOP Proxy)
Object exposedObject = initializeBean(beanName, bean, mbd);
// 6. Регистрация DisposableBean для синглтонов (если scope = singleton)
registerDisposableBeanIfNecessary(beanName, bean, mbd);
return exposedObject;
}
А метод initializeBean детализирует цепочку инициализации:
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
// Вызов Aware: BeanNameAware, BeanClassLoaderAware, BeanFactoryAware
invokeAwareMethods(beanName, bean);
// Вызов BPP Before (здесь @PostConstruct и ApplicationContextAware)
Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);
// Вызов InitializingBean.afterPropertiesSet() и кастомного init-method
invokeInitMethods(beanName, wrappedBean, mbd);
// Вызов BPP After (ЗДЕСЬ СОЗДАЕТСЯ AOP PROXY!)
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
return wrappedBean;
}
Проблема ранней инициализации BPP (BeanPostProcessor)
Один из самых сложных дефектов в production-системах на Spring:
Bean 'myService' of type [com.example.MyService] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
Причина возникновения:
BeanPostProcessor— это системные компоненты контейнера, которые должны инициализироваться раньше всех обычных пользовательских бинов, чтобы успевать обрабатывать их создание.- Если какой-то кастомный
BeanPostProcessorслучайно внедряет в себя бизнес-сервис (например,UserRepositoryилиAuditServiceчерез обычный@Autowired):@Component public class BadCustomBpp implements BeanPostProcessor { @Autowired private AuditService auditService; // ОШИБКА АРХИТЕКТУРЫ! } - Контейнер вынужден экстренно создать
AuditServiceдо того, как будут зарегистрированы остальные системныеBeanPostProcessor(включаяAnnotationAwareAspectJAutoProxyCreator). - Результат:
AuditServiceсоздается «голым» объектом. Он не получит ни@Transactional, ни@Async, ни кэширование@Cacheable. Приложение запустится, но бизнес-транзакции вAuditServiceработать не будут!
Как избежать:
- Использовать ленивую инъекцию через
ObjectProvider<AuditService>или@Lazy:@Component public class GoodCustomBpp implements BeanPostProcessor { private final ObjectProvider<AuditService> auditServiceProvider; public GoodCustomBpp(ObjectProvider<AuditService> auditServiceProvider) { this.auditServiceProvider = auditServiceProvider; } // Обращаться к auditServiceProvider.getIfAvailable() только в момент выполнения }
Точка создания прокси при циклических зависимостях
В стандартном сценарии AOP-прокси создается на шаге 7 (BPP After Initialization). Однако, если бин вовлечен в циклическую зависимость (A → B → A):
- Бин A инстанцируется и регистрирует фабрику в
singletonFactories. - Бин A начинает наполняться зависимостями (
populateBean) и запрашивает бин B. - Бин B инстанцируется и запрашивает зависимость A.
- Контейнер забирает
ObjectFactoryиз 3-го кэша, которая вызываетSmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference(beanName, mbd, bean). - Внимание: если бин A требует проксирования (например, имеет метод
@Transactional), именно на этом раннем этапе создается AOP-прокси для A и кладется во 2-й кэшearlySingletonObjects. - Когда бин A наконец доходит до шага 7 (
postProcessAfterInitialization), автопрокси-создатель видит, что ранний прокси уже был создан, и не оборачивает бин повторно, а возвращает уже существующий прокси из кэша.
🎯 Шпаргалка для интервью
30-секундный ответ
Жизненный цикл бина в Spring насчитывает 12 ключевых фаз:
- Создание экземпляра (Instantiation): вызов конструктора.
- Наполнение зависимостями (Populate Properties): инъекция полей и сеттеров.
- Aware-интерфейсы:
BeanNameAware,BeanFactoryAware.- BPP Before Initialization: выполнение
@PostConstructи передачаApplicationContext.InitializingBean.afterPropertiesSet().- Кастомный
init-method.- BPP After Initialization: создание AOP-прокси (
@Transactional,@Async).- Готовность (In Service).
- BPP Destruction /
@PreDestroy.DisposableBean.destroy().- Кастомный
destroy-method.- Для scope
prototypeэтапы 9–11 контейнером не вызываются.
4 каверзных вопроса с ответами
- В каком порядке вызываются
@PostConstruct,afterPropertiesSet()иinitMethod? Ответ: Строго в следующем порядке:@PostConstruct(обрабатываетсяCommonAnnotationBeanPostProcessorв фазе BPP Before).InitializingBean.afterPropertiesSet()(прямой вызов контракта интерфейса).initMethod(декларативный метод из@Bean(initMethod = "...")). В современной разработке рекомендуется использовать@PostConstruct, так как это стандартный механизм Java/Jakarta, не завязывающий код на интерфейсы Spring Framework.
-
На каком этапе жизненного цикла создается AOP-прокси и к каким эффектам это приводит? Ответ: В стандартном сценарии AOP-прокси создается на шаге
BeanPostProcessor.postProcessAfterInitialization()(черезAbstractAutoProxyCreator). Это означает, что во время выполнения@PostConstructи Aware-интерфейсов объект является исходным target-экземпляром (сырым объектом без прокси). Следовательно, любые вызовы собственных методов с аннотациями@Transactional,@Asyncили@Cacheableиз@PostConstructвыполняются без интерцепторов прокси! -
Что означает предупреждение «Bean is not eligible for getting processed by all BeanPostProcessors»? Ответ: Это означает, что бин был создан раньше времени, так как потребовался как зависимость для какого-либо
BeanPostProcessor(BPP). Поскольку он был создан до инициализации всех BPP, к нему не были применены зарегистрированные позже пост-процессоры, в частности генераторы AOP-прокси (AnnotationAwareAspectJAutoProxyCreator). Такой бин не получит транзакционность и другие аспекты. - В каком порядке вызываются методы уничтожения бина при остановке контекста?
Ответ: Порядок строго обратный инициализации:
- Метод с аннотацией
@PreDestroy. - Метод
destroy()интерфейсаDisposableBean. - Кастомный метод из декларации
@Bean(destroyMethod = "...").
- Метод с аннотацией
Красные флаги (чего категорически нельзя говорить)
- ❌ «AOP-прокси создается во время работы конструктора» — прокси создается только на этапе
postProcessAfterInitialization(или при early singleton exposure в случае циклов). - ❌ «
@PreDestroyвызывается для prototype-бинов» — контейнер полностью отпускает prototype-бины после этапа инициализации и никогда не вызывает для них методы очистки. - ❌ «
@PostConstructвызывается до внедрения зависимостей» —@PostConstructвызывается строго ПОСЛЕ того, как все зависимости (поля/сеттеры) внедрены. - ❌ «Можно безопасно инжектировать сервисы баз данных в BeanPostProcessor» — это ломает порядок создания бинов и лишает сервис поддержки AOP-прокси.