Питання 8 · Розділ 5

Що таке BeanPostProcessor?

Для application-коду зазвичай достатньо @Aspect. Кастомні BPP потрібні рідко — здебільшого при написанні бібліотек.

Мовні версії: English Russian Ukrainian

🟢 Junior Level

BeanPostProcessor (BPP) — це механізм Spring для зміни бінів після їх створення.

Навіщо: BPP потрібен, коли ви хочете додати поведінку до бінів без зміни їхнього коду. Наприклад: створити проксі для транзакцій (@Transactional), додати логування (@Aspect), обгорнути в кеш (@Cacheable). Через BPP працюють УСІ Spring-анотації, що додають поведінку.

Проста аналогія: Фабрика випускає іграшки, а BPP — це відділ контролю, який може додати наклейку, упаковку або взагалі замінити іграшку.

@Component
public class MyBPP implements BeanPostProcessor {

    // ДО ініціалізації (@PostConstruct)
    public Object postProcessBeforeInitialization(Object bean, String name) {
        return bean;  // Можна змінити або замінити
    }

    // ПІСЛЯ ініціалізації
    public Object postProcessAfterInitialization(Object bean, String name) {
        return bean;  // Тут створюються проксі!
    }
}

🟡 Middle Level

Що робить BPP

1. Конструктор біна
2. Впровадження залежностей
3. BPP.postProcessBeforeInitialization()
4. @PostConstruct
5. BPP.postProcessAfterInitialization() ← Проксі створюється тут!
6. Бін готовий

Створення проксі

@Override
public Object postProcessAfterInitialization(Object bean, String name) {
    if (bean.getClass().isAnnotationPresent(Transactional.class)) {
        // Створюємо проксі з транзакціями
        return proxyFactory.createProxy(bean);
    }
    return bean;  // Звичайний бін без змін
}

Кастомні анотації

// Своя анотація
@Target(METHOD)
@Retention(RUNTIME)
public @interface LogExecution { }

// BPP для логування
@Component
public class LoggingBPP implements BeanPostProcessor {
    public Object postProcessAfterInitialization(Object bean, String name) {
        // Створюємо проксі для логування методів з @LogExecution
        return LoggingProxy.create(bean);
    }
}

BPP vs BeanFactoryPostProcessor

BeanFactoryPostProcessor:
  → Працює з BeanDefinition (до створення бінів)
  → Змінює рецепти

BeanPostProcessor:
  → Працює з екземплярами бінів
  → Змінює об'єкти

🔴 Senior Level

BPP vs @Aspect: що обрати

BPP працює на рівні створення бінів (один раз під час старту). Використовуйте для: реєстрації бінів, зміни структури, обгортання в проксі.

@Aspect працює на рівні викликів методів (при кожному виклику). Використовуйте для: логування, транзакцій, кешування, моніторингу.

Для application-коду зазвичай достатньо @Aspect. Кастомні BPP потрібні рідко — здебільшого при написанні бібліотек.

Коли НЕ використовувати кастомний BPP

  1. Application-код — зазвичай достатньо @Aspect або стандартних анотацій
  2. Просте логування — використовуйте @Aspect + @Around
  3. Зміна одного біна — краще відредагувати сам бін або використати @Bean метод

InstantiationAwareBeanPostProcessor

Розширена версія BPP, яка дозволяє втрутитися ЩЕ ДО створення об’єкта (postProcessBeforeInstantiation) і ВІДРАЗУ ПІСЛЯ конструктора, але ДО впровадження залежностей (postProcessAfterInstantiation).

// Розширений BPP:
public class MyBPP implements InstantiationAwareBeanPostProcessor {

    // ДО створення об'єкта
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String name) {
        // Можна повернути проксі замість створення реального об'єкта
        return null;  // null = створити як звичайно
    }

    // ПІСЛЯ конструктора, ДО впровадження залежностей
    public boolean postProcessAfterInstantiation(Object bean, String name) {
        return true;  // true = продовжити впровадження
    }
}

Рання ініціалізація BPP

Якщо BPP залежить від MyService:
  → MyService створюється ДО реєстрації всіх BPP
  → MyService може не отримати проксі!

Рішення:
  → @Lazy залежності в BPP
  → Або незалежні BPP

BPP Ordering

@Order(1)
public class FirstBPP implements BeanPostProcessor { }

@Order(2)
public class SecondBPP implements BeanPostProcessor { }

 Порядок важливий!
 Перший BPP може замінити бін  другий отримає вже замінений

Production Experience

Реальний сценарій: кастомна анотація @Cache

// Своя анотація @CustomCache
@CustomCache(ttl = 3600)
public Data getData() { }

// BPP створює проксі з кешуванням
// → Виклик → перевірка кешу → якщо немає → метод → зберегти в кеш

Best Practices

  1. BPP → розширення Spring під свої потреби
  2. postProcessAfterInitialization → створення проксі
  3. @Order → управління порядком
  4. @Lazy → уникайте ранньої ініціалізації
  5. Не проксируйте BPP → вони створюються рано
  6. InstantiationAwareBPP → контроль до створення

Резюме для Senior

  • BPP → зміна бінів після створення
  • Proxy створюється в postProcessAfterInitialization
  • InstantiationAwareBPP → контроль ДО створення
  • Order → важливий при кількох BPP
  • Рання ініціалізація → може зламати проксі
  • BFPP vs BPP → мета-дані vs екземпляри

🎯 Шпаргалка для співбесіди

Обов’язково знати:

  • BPP (BeanPostProcessor) — механізм Spring для зміни/обгортання бінів після створення
  • Два методи: postProcessBeforeInitialization() (до @PostConstruct) і postProcessAfterInitialization() (після, тут створюються проксі!)
  • Через BPP працюють УСІ Spring-анотації, що додають поведінку: @Transactional, @Async, @Cacheable, @Aspect
  • BPP vs BFPP: BPP працює з екземплярами бінів, BFPP — з BeanDefinition (до створення бінів)
  • InstantiationAwareBeanPostProcessor — розширена версія: контроль ДО створення і ПІСЛЯ конструктора, але ДО DI
  • Порядок BPP через @Order важливий — перший може замінити бін, другий отримає вже замінений
  • Для application-коду зазвичай достатньо @Aspect; кастомні BPP потрібні рідко (здебільшого для бібліотек)
  • Рання ініціалізація: якщо BPP залежить від біна, той може не отримати проксі — використовуйте @Lazy

Часті уточнюючі запитання:

  • Де створюється проксі для @Transactional? В postProcessAfterInitialization() — після @PostConstruct.
  • Коли обрати BPP vs @Aspect? @Aspect — для application-коду (логування, моніторинг). BPP — для бібліотек, реєстрації бінів, зміни структури.
  • Що повертає postProcessBeforeInstantiation? Можна повернути проксі замість створення об’єкта, або null — тоді Spring створить як звичайно.
  • Чому BPP не можна проксирувати? Вони створюються рано в життєвому циклі контейнера — можуть не отримати свою проксі-обгортку.

Червоні прапорці (НЕ говорити):

  • «BPP — це основний інструмент для application-коду» (для додатку зазвичай вистачає @Aspect, BPP — для бібліотек)
  • «BPP і BFPP — одне й те саме» (BFPP змінює BeanDefinition до створення, BPP змінює екземпляри після)
  • «Порядок BPP не має значення» (критично важливий — перший BPP може замінити бін)
  • «Можна вільно інжектити біни в BPP» (викликає ранню ініціалізацію, ламає проксі)

Пов’язані теми:

  • [[6. Що таке Bean Lifecycle]]
  • [[7. Які етапи є у Bean lifecycle]]
  • [[9. Що роблять методи з анотаціями @PostConstruct та @PreDestroy]]
  • [[4. Що таке Bean в Spring]]
  • [[1. Що таке Dependency Injection]]