Spring FrameworkSeniorTechnical

Что такое BeanPostProcessor и BeanFactoryPostProcessor и чем они отличаются?

BeanFactoryPostProcessor изменяет BeanDefinition-метаданные до создания бинов (например, PropertySourcesPlaceholderConfigurer). BeanPostProcessor оборачивает или заменяет готовые экземпляры до/после инициализации — здесь Spring AOP создаёт CGLIB/JDK-прокси для @Transactional и @Cacheable.

BeanFactoryPostProcessor и BeanPostProcessor

Это два разных extension point контейнера Spring, работающих на разных уровнях и в разное время жизненного цикла.

  • BeanFactoryPostProcessor — вызывается после загрузки всех BeanDefinition, но до создания любого бина. Позволяет изменять метаданные бинов (BeanDefinition): scope, class, свойства. Типичный пример — PropertySourcesPlaceholderConfigurer, который подставляет ${db.url} до того, как создан хоть один бин.
  • BeanPostProcessor — вызывается дважды вокруг инициализации каждого созданного бина: до и после @PostConstruct/afterPropertiesSet(). Позволяет заменять или оборачивать экземпляры. Именно здесь AnnotationAwareAspectJAutoProxyCreator создаёт AOP-прокси.

Интерфейсы

// Изменяет метаданные — работает ДО создания бинов
@FunctionalInterface
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
            throws BeansException;
}

// Оборачивает/заменяет экземпляры — работает ПОСЛЕ создания каждого бина
public interface BeanPostProcessor {
    default Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }
    default Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }
}

Пример: BeanFactoryPostProcessor — принудительный scope

@Component
public class ForceScopePostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) {
        // Меняем scope бина на prototype до его создания
        BeanDefinition def = factory.getBeanDefinition("paymentService");
        def.setScope(BeanDefinition.SCOPE_PROTOTYPE);

        // Подсмотреть все бины определённого типа
        String[] names = factory.getBeanNamesForType(DataSource.class);
        for (String name : names) {
            BeanDefinition bd = factory.getBeanDefinition(name);
            System.out.println(name + " -> " + bd.getBeanClassName());
        }
    }
}

Пример: BeanPostProcessor — кастомная аннотация

// Аннотация для логирования времени выполнения методов
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timed {}

@Component
public class TimedBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean.getClass().isAnnotationPresent(Timed.class)) {
            // Создаём JDK Dynamic Proxy
            return Proxy.newProxyInstance(
                bean.getClass().getClassLoader(),
                bean.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    long start = System.nanoTime();
                    try {
                        return method.invoke(bean, args);
                    } finally {
                        long elapsed = System.nanoTime() - start;
                        System.out.printf("%s.%s took %d ms%n",
                            beanName, method.getName(), elapsed / 1_000_000);
                    }
                }
            );
        }
        return bean;
    }
}

@Timed
@Service
public class OrderService implements OrderServiceI {
    public void processOrder(Long id) { /* ... */ }
}

Встроенные реализации в Spring

  • PropertySourcesPlaceholderConfigurerBeanFactoryPostProcessor, резолвит ${...} плейсхолдеры.
  • ConfigurationClassPostProcessorBeanFactoryPostProcessor, обрабатывает @Configuration, @Bean, @Import.
  • AutowiredAnnotationBeanPostProcessorBeanPostProcessor, обрабатывает @Autowired и @Value.
  • CommonAnnotationBeanPostProcessorBeanPostProcessor, обрабатывает @PostConstruct, @PreDestroy, @Resource.
  • AnnotationAwareAspectJAutoProxyCreatorBeanPostProcessor, создаёт AOP-прокси для @Transactional, @Cacheable и т.д.

Подводные камни

  • BeanFactoryPostProcessor нельзя инжектировать в него зависимости через @Autowired — он создаётся раньше всех остальных бинов, и обычные бины ещё не существуют.
  • Если BeanPostProcessor зависит от обычного бина (@Autowired SomeService), Spring предупредит, что этот бин «not eligible for auto-proxying» — BeanPostProcessor-ы создаются до AOP-прокси.
  • Если postProcessAfterInitialization вернул другой объект (прокси), то тип прокси должен совпадать с типом, ожидаемым при инжекции — иначе ClassCastException при getBean().
  • Метод postProcessBeforeInitialization, вернувший null, остановит цепочку обработки — Spring выбросит исключение. Всегда возвращайте ненулевое значение.
  • Порядок выполнения нескольких BeanPostProcessor-ов управляется через интерфейс Ordered или аннотацию @Order.
  • BeanFactoryPostProcessor работает только с singleton и prototype бинами из основного контекста — бины web-scope (request, session) на этом этапе ещё не зарегистрированы.
  • Не нужно вручную регистрировать BeanPostProcessor в ApplicationContext — контейнер автоматически обнаруживает все бины, реализующие этот интерфейс. В bare BeanFactory нужна ручная регистрация.
  • Изменение BeanDefinition внутри BeanPostProcessor (а не BeanFactoryPostProcessor) — антипаттерн: бин уже создан, изменять его метаданные поздно и небезопасно.

Common mistakes

  • Путать термин «bean post processors» с соседним механизмом Spring Framework.
  • Не называть границу lifecycle, transaction, thread или request для «bean post processors».
  • Игнорировать production-эффекты «bean post processors»: latency, SQL shape, memory, security или observability.

What the interviewer is testing

  • Попросить объяснить механизм «bean post processors» на минимальном примере.
  • Проверить, видит ли кандидат failure mode и диагностику для «bean post processors».
  • Уточнить, какие настройки или API меняют «bean post processors» в реальном сервисе.

Sources

Related topics