Как в Spring приложении подменить один бин другим и зачем это нужно
Что проверяют
Понимаете ли вы, как контейнер Spring выбирает бин из нескольких кандидатов и как переопределяются определения бинов. Следом обычно спрашивают про тесты: чем @MockitoBean отличается от обычного мока и почему тесты с ним бывают медленными.
Как отвечать
Сначала зачем: подменить реализацию под окружение (заглушка платёжного шлюза на dev-стенде), под тест или вместо бина, который создаёт автоконфигурация Spring Boot. Клиентский код зависит от интерфейса и о подмене ничего не знает.
Способы:
@Primary: при нескольких бинах одного типа внедряется помеченный. Точечно выбрать другой можно через@Qualifier;@Profile: реализация создаётся только в нужном профиле, например@Profile("dev")для заглушки;- условия:
@ConditionalOnPropertyвключает бин по настройке, а автоконфигурации Boot помечены@ConditionalOnMissingBean. Объявите свойDataSource, и стандартный не создастся; - тесты:
@MockitoBeanи@MockitoSpyBeanиз Spring Framework 6.2 заменяют бин в контексте моком,@TestBeanподставляет объект из фабричного метода,@TestConfigurationдобавляет свои бины. Старый@MockBeanпомечен устаревшим начиная со Spring Boot 3.4; - низкий уровень:
BeanPostProcessorоборачивает готовый бин,BeanFactoryPostProcessorменяет определения до создания.
Отдельно скажите про переопределение по имени. Начиная со Spring Boot 2.1 spring.main.allow-bean-definition-overriding по умолчанию false, и два бина с одним именем дают BeanDefinitionOverrideException при старте. Это защита от случайной подмены, включать флаг ради удобства не стоит.
Где спотыкаются
Ставят @Component на интерфейс. Сканер компонентов интерфейсы пропускает, аннотация ничего не делает.
Внедряют конкретный класс вместо интерфейса. Тогда подменить реализацию нечем, кроме наследования.
Раскидывают @MockitoBean с разными наборами моков по тестовым классам. Каждый уникальный набор создаёт новый контекст, кеш контекстов не срабатывает, и прогон тестов растягивается в разы.