Spring Не удалось подключить автоматически, существует более одного компонента типа
Вот мой вопрос: у меня есть базовый интерфейс и два класса реализации.
И класс Service имеет зависимости от базового интерфейса, код выглядит так:
@Component
public interface BaseInterface {}
@Component
public class ClazzImplA implements BaseInterface{}
@Component
public class ClazzImplB implements BaseInterface{}
И конфигурация такая:
@Configuration
public class SpringConfig {
@Bean
public BaseInterface clazzImplA(){
return new ClazzImplA();
}
@Bean
public BaseInterface clazzImplB(){
return new ClazzImplB();
}
}
Класс сервиса имеет зависимости от базового интерфейса, и он решит автоматически связывать реализацию с некоторой бизнес-логикой. И код выглядит так:
@Service
@SpringApplicationConfiguration(SpringConfig.class)
public class AutowiredClazz {
@Autowired
private BaseInterface baseInterface;
private AutowiredClazz(BaseInterface baseInterface){
this.baseInterface = baseInterface;
}
}
И ИДЕЯ выдает исключение: не удалось выполнить автоматическое подключение. Существует более одного компонента BaseInterface
тип.
Хотя это можно решить с помощью @Qualifier, но в этой ситуации я не могу выбрать класс зависимостей.
@Autowired
@Qualifier("clazzImplA")
private BaseInterface baseInterface;
Я пытался прочитать весенний документ, и он предоставляет Constructor-based dependency injection
но я все еще смущен этой проблемой.
Может кто-нибудь мне помочь?
4 ответа
Spring перепутан между двумя bean-компонентами, которые вы объявили в своем классе конфигурации, поэтому вы можете использовать @Qualifier
аннотация вместе с @Autowired
чтобы устранить путаницу, указав, какой именно bean-компонент будет подключен, примените эти изменения к вашему классу конфигурации
@Configuration
public class SpringConfig {
@Bean(name="clazzImplA")
public BaseInterface clazzImplA(){
return new ClazzImplA();
}
@Bean(name="clazzImplB")
public BaseInterface clazzImplB(){
return new ClazzImplB();
}
}
тогда в @autowired
аннотирование
@Service
@SpringApplicationConfiguration(SpringConfig.class)
public class AutowiredClazz {
@Autowired
@Qualifier("the name of the desired bean")
private BaseInterface baseInterface;
private AutowiredClazz(BaseInterface baseInterface){
this.baseInterface = baseInterface;
}
}
Это не может быть решено с помощью только пружинного каркаса. Вы упомянули, что, основываясь на некоторой логике, вам нужен экземпляр BaseInterface. Этот вариант использования может быть решен с помощью Factory Pattern. Создайте Бин, который на самом деле является фабрикой для BaseInterface
@Component
public class BaseInterfaceFactory{
@Autowired
@Qualifier("clazzImplA")
private BaseInterface baseInterfaceA;
@Autowired
@Qualifier("clazzImplb")
private BaseInterface baseInterfaceB;
public BaseInterface getInstance(parameters which will decides what type of instance you want){
// your logic to choose Instance A or Instance B
return baseInterfaceA or baseInterfaceB
}
}
Конфигурация (бесстыдно скопировано из другого комментария)
@Configuration
public class SpringConfig {
@Bean(name="clazzImplA")
public BaseInterface clazzImplA(){
return new ClazzImplA();
}
@Bean(name="clazzImplB")
public BaseInterface clazzImplB(){
return new ClazzImplB();
}
}
Сервисный класс
@Service
@SpringApplicationConfiguration(SpringConfig.class)
public class AutowiredClazz {
@Autowired
private BaseInterfaceFactory factory;
public void someMethod(){
BaseInterface a = factory.getInstance(some parameters);
// do whatever with instance a
}
}
Если вы используете @Autowired
Spring ищет bean-компонент, соответствующий типу поля, которое вы хотите автоматически связать. В вашем случае есть более одного бина типа BaseInterface
, Это означает, что Spring не может однозначно выбрать подходящий компонент.
В такой ситуации у вас нет другого выбора явно заявить, что Spring должен использовать bean-компонент или устранить неоднозначность.
Еще более прохладное решение - позволить самим реализациям содержать логику, чтобы определить, применимы ли они. Вы можете внедрить все доступные реализации в виде коллекции и перебрать их, чтобы найти одну (или более, если это то, что вам нужно) применимые:
public interface BaseInterface {
boolean canHandle(Object parameter);
Object doTheWork(Object parameter);
}
@Service
public class SomeService {
private final BaseInterface[] implementations;
// Spring injects all beans implementing BaseInterface
public MessageService(BaseInterface... implementations) {
this.implementations = implementations;
}
public Object doSomething(Object parameter) {
BaseInterface baseInterface = findBaseInterface(parameter);
return baseInterface.doTheWork(parameter);
}
private BaseInterface findBaseInterface(Object parameter) {
return Arrays.stream(implementations)
.findAny(i -> i.canHandle(parameter)
.orElseThrow(new MyRuntimeException(String.format("Could not find BaseInterface to handle %s", parameter)));
}
}
Здесь справедливо ответили, что в тех случаях, когда интерфейс реализуется более чем одним классом, мы должны называть каждый из этих компонентов с помощью @Component(name=$beanName)
Я хотел бы добавить еще одно замечание, что в таких случаях Spring может даже автоматически связывать такие компоненты на карте:
@Autowired
Map<String,InterfaceName> interfaceMap;
//this will generate beans and index them with beanName as key of map