javaspring-bootdependency-injection

How to inject multiple implementations of same type with Spring Boot?


My class RssStrategy injects a RssConsumer class. There are multiple implementations of the RssConsumer; therefore, Spring Boot throws an error that multiple beans of the same type have been found.

Exception

Parameter 0 of constructor in <path>.RssStrategy required a single bean, but 11 were found:

How would I circumvent this issue with my current implementation? I have seen solutions where a qualifier has been used to determine which implementation to reference, but in my case, RssStrategy.java doesn't know, and it's up to the service that injects the Strategy to decide.

RssStrategy.java

@Component
public class RssStrategy<T extends RssConsumer> implements FetchDataStrategy{

   private final T rssConsumer;

   public RssStrategy(T rssConsumer) {
       this.rssConsumer = rssConsumer;
   }
    
   @Override
   public void fetchData() {
      rssConsumer.consume();
   }
}

Different implementations of RssConsumer.java:

@Component(value = "rssConsumerA")
public class RssConsumerA extends RssConsumer {
   @Override
   public void consume() {}
}

@Component(value = "rssConsumerB")
public class RssConsumerB extends RssConsumer {
   @Override
   public void consume() {}
}

Injecting RssStrategy in different services but always passing a different generic type implementation:

@Service
public class ServiceA extends MyAbstractService {
   public ServiceA ( RssStrategy<RssConsumerA> fetchDataStrategy) {}
   
   @Override
   public void run() {fetchDataStrategy.fetchData()}
}

@Service
public class ServiceB extends MyAbstractService {
   public ServiceB ( RssStrategy<RssConsumerB> fetchDataStrategy) {}
   
   @Override
   public void run() {fetchDataStrategy.fetchData()}
}

Update: I figured out a solution:

The only alternative I found out so far is instead of injecting the rssConsumer, I provide it via setter like:

@Component
public class RssStrategy<T extends RssConsumer> implements FetchDataStrategy{

   private final T rssConsumer;

   public setRssConsumer(T rssConsumer) {
      this.rssConsumer = rssConsumer;
   }

   @Override
   public void fetchData() {
      rssConsumer.consume();
   }
}

And injecting the Consumer in the Service instead and providing it via the setter method:

@Service
public class ServiceA extends MyAbstractService {
      public ServiceA ( RssConsumerA, rssConsumerA, 
   RssStrategy<RssConsumerA> fetchDataStrategy) {
      fetchDataStrategy.setRssConsumer(rssConsumerA);
   }

   @Override
   public void run() {fetchDataStrategy.fetchData()}
}

Update 2: It seems that my solution also does not work. Even though I provide the rssConsumer via setter:

fetchDataStrategy.setRssConsumer(rssConsumerA);

The actual consumer seems to be rather random. I have to investigate more but it's not the correct behaviour.


Solution

  • Your solution has a drawback! Spring components are Singleton by default, so when working in a concurrent environment (such as web application), your code breaks!

    Thread 1 calls setConsumer(consumer1), and just before calling the second method threa2 calls setConsumer(consumer2) and now thread 1's method is called on consumer2.

    To prevent the problem change the signature of your method to fetchData(Consumer consumer), now you are sure using proper consumer.


    Another solution can be using a factory, if you can determine which consumer should be used according to fetchData arguments, so define a class as

    @Component
    public class ConsumerFactory implements BeanFactoryAware {
       private BeanFactory beanFactory;
    
       public void setBeanFactory(BeanFactory beanFactory) {
          this.beanFactory = beanFactory;
       }
    
       public Consumer resolverConsumer(arguments) {
          if (condition1) {
             return beanFactory.getBean('name1', Consumer.class);
          } 
          if (condition2) {
             return beanFactory.getBean('name2', Consumer.class);
          } 
          ....
       }
    
    }
    

    now inject this factory in your strategy, and you can get proper Consumer in your Strategy.


    Never forget! as Spring components are singleton by default, you must not have conversional state in them!