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
PropertySourcesPlaceholderConfigurer—BeanFactoryPostProcessor, резолвит${...}плейсхолдеры.ConfigurationClassPostProcessor—BeanFactoryPostProcessor, обрабатывает@Configuration,@Bean,@Import.AutowiredAnnotationBeanPostProcessor—BeanPostProcessor, обрабатывает@Autowiredи@Value.CommonAnnotationBeanPostProcessor—BeanPostProcessor, обрабатывает@PostConstruct,@PreDestroy,@Resource.AnnotationAwareAspectJAutoProxyCreator—BeanPostProcessor, создаёт 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— контейнер автоматически обнаруживает все бины, реализующие этот интерфейс. В bareBeanFactoryнужна ручная регистрация. - Изменение
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» в реальном сервисе.