1

I am trying to implement factory pattern to get producer from a list of available ones. While doing it i am getting the below exception. Not able to figure out the issue with the code. Can you please let me know what i am missing.

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.test.interfaces.Producer] is defined: expected single matching bean but found 2: A,B

Please find the code below

    public interface Producer<T>  {
    public void start();
    public List<T> produce() throws CEHServiceException;
    public void stop();
}

@Component("A")
public class ProducerA extends Producer  {
    //Autowire Services & Properties

}

@Component("B")
public class ProducerB extends Producer  {
    //Autowire Services & Properties
}

@Configuration
public class AgentConfiguration {
    @Bean
    public ServiceLocatorFactoryBean createProducerFactoryBean(){
        ServiceLocatorFactoryBean bean = new ServiceLocatorFactoryBean();
        bean.setServiceLocatorInterface(ProducerFactory.class);
        return bean;
    }
}

public interface ProducerFactory {

    Producer getProducer(String producerName);

}

@Component
public class AdvancedAgentProcessor {

    @Autowired
    private ObjectFactory<AdvancedRunnerImpl> runnerFactory;
    public void init(){
        AdvancedRunnerImpl runner = runnerFactory.getObject();
        runner.setProducerName("A");
        runner.start();
    }

}


@Component
@Scope("prototype")
public class AdvancedRunnerImpl implements Runner {

    @Autowired private ProducerFactory producerFactory;
    private Producer producer;
    private String producerName;

    public void start() {
        producer = producerFactory.getProducer(this.producerName);
    }

}

Full stack tracke

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.test.etl.interfaces.Producer] is defined: expected single matching bean but found 2: A,B
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:365)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331)
    at org.springframework.beans.factory.config.ServiceLocatorFactoryBean$ServiceLocatorInvocationHandler.invokeServiceLocatorMethod(ServiceLocatorFactoryBean.java:377)
    at org.springframework.beans.factory.config.ServiceLocatorFactoryBean$ServiceLocatorInvocationHandler.invoke(ServiceLocatorFactoryBean.java:363)
    at com.sun.proxy.$Proxy34.getProducer(Unknown Source)
    at com.test.runner.AdvancedRunnerImpl.start(AdvancedRunnerImpl.java:54)
    at com.test.app.AdvancedAgentProcessor.init(AdvancedAgentProcessor.java:48)
    at com.test.app.DataAgentApplication.main(DataAgentApplication.java:25)
moffeltje
  • 4,521
  • 4
  • 33
  • 57
Arul
  • 143
  • 3
  • 12
  • 1
    What is your ProducerFactory implementation? – gabrielgiussi Feb 01 '16 at 17:18
  • Is anything else in your `ApplicationContext` trying to autowire an instance of a single `Producer` anywhere? Can you provide the whole stacktrace? – nicholas.hauschild Feb 01 '16 at 17:18
  • 1
    @gabrielgiussi it is a dynamic proxy that Spring will create based on the presence of the `ServiceLocatorFactoryBean`. http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.html – nicholas.hauschild Feb 01 '16 at 17:19
  • @nicholas.hauschild Had updated the post with stack trace. I am using spring boot 1.2.7 and i don't use ApplicationContext directly anywhere. – Arul Feb 01 '16 at 17:46

3 Answers3

2

Spring does not know which component to autowire. It seems that the problem is in the ProducerFactoryImplementation but we cannot see it.

There are three possible solutions:

  1. Use Qualifiers so you can tell Spring which specific implementation you want.There is an example in StackOverflow here

  2. Use the Primary annotation (See more here3). That means that in case of ambiguity Spring will give priority to the @Primary annotated component

  3. Autowire a list of beans. Something like:

    @Autowired private List<Producer> myAvalilableProducers;
    
    public Producer getByName(name){
    
        for( Producer producer: myAvalilableProducers){
            if(producer.getName().equals(name)){ return producer; }
        }
        throw new RuntimeException("No producer with name " + name " found");
    }
    

This third option more useful when you do not know the specific instance at compile time or if you really want to inject a list of components.

borjab
  • 11,149
  • 6
  • 71
  • 98
  • This works. But introduces two issues, i need to introduce a new field called producer name and any new producers i add will need to change the code in Runner. i will go ahead and implement it. Thanks. – Arul Feb 02 '16 at 03:57
  • You my avoid the name field by using the qualifiers like in the example I have just linked. Or you could use getClass().getAnnnotation(Qualifier.class).vale instead of name – borjab Feb 02 '16 at 09:07
0

You have two beans that extend Producer. Somewhere you are trying to autowire a Producer. Spring does not know which Producer to use.

Raedwald
  • 46,613
  • 43
  • 151
  • 237
0

This happens when the dynamic proxy is not able to pick the correct Bean. Please check whether this.producerName is null or empty.

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77