Які етапи є у Bean lifecycle
Життєвий цикл біна у Spring — це сувора послідовність кроків, через які проходить об'єкт із моменту сканування його визначення (BeanDefinition) до знищення під час вимкнення зас...
🟢 Junior Level
Життєвий цикл біна у Spring — це сувора послідовність кроків, через які проходить об’єкт із моменту сканування його визначення (BeanDefinition) до знищення під час вимкнення застосунку.
5 основних кроків (спрощена модель)
1. Instantiation (Конструктор) → Створення об'єкта в пам'яті (Heap)
↓
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 каверзних питання з відповідями
1. У якому порядку викликаються @PostConstruct, afterPropertiesSet() та initMethod?
Відповідь: Суворо у наступному порядку:
@PostConstruct(обробляєтьсяCommonAnnotationBeanPostProcessorу фазі BPP Before).InitializingBean.afterPropertiesSet()(прямий виклик контракту інтерфейсу).initMethod(декларативний метод із@Bean(initMethod = "...")). У сучасній розробці рекомендується використовувати@PostConstruct, оскільки це стандартний механізм Java/Jakarta, що не прив’язує бізнес-код до інтерфейсів Spring Framework.
2. На якому етапі життєвого циклу створюється AOP-проксі і до яких ефектів це призводить?
Відповідь: У стандартному сценарії AOP-проксі створюється на кроці BeanPostProcessor.postProcessAfterInitialization() (через AbstractAutoProxyCreator). Це означає, що під час виконання @PostConstruct та методів Aware-інтерфейсів об’єкт є вихідним target-екземпляром (сирим об’єктом без перехоплювачів). Отже, будь-які виклики власних методів з анотаціями @Transactional, @Async чи @Cacheable з методу @PostConstruct виконуються без активації проксі-перехоплювачів!
3. Що означає попередження «Bean is not eligible for getting processed by all BeanPostProcessors»?
Відповідь: Це означає, що бін був створений передчасно, оскільки виявився залежністю для якогось BeanPostProcessor (BPP). Оскільки його було створено до повної реєстрації решти BPP, до нього не застосувалися пізніше зареєстровані пост-процесори, зокрема генератори AOP-проксі (AnnotationAwareAspectJAutoProxyCreator). Такий бін залишиться без підтримки транзакцій та інших аспектів.
4. У якому порядку викликаються методи знищення біна під час зупинки контексту?
Відповідь: Порядок суворо зворотний до ініціалізації:
- Метод з анотацією
@PreDestroy. - Метод
destroy()інтерфейсуDisposableBean. - Кастомний метод із декларації
@Bean(destroyMethod = "...").
Червоні прапорці (чого категорично не можна говорити)
- ❌ «AOP-проксі створюється під час роботи конструктора» — проксі створюється лише на етапі
postProcessAfterInitialization(або при early singleton exposure у випадку циклів). - ❌ «
@PreDestroyвикликається для prototype-бінів» — контейнер повністю відпускає prototype-біни після етапу ініціалізації і ніколи не викликає для них методи очищення. - ❌ «
@PostConstructвикликається до впровадження залежностей» —@PostConstructвикликається суворо ПІСЛЯ того, як усі залежності (поля/сетери) впроваджено. - ❌ «Можна безпечно інжектувати бізнес-сервіси баз даних у BeanPostProcessor» — це порушує послідовність створення бінів і позбавляє сервіс підтримки AOP-проксі.