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
    }
}

Если вы используете @AutowiredSpring ищет 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
Другие вопросы по тегам