I have a Spring configuration class like this where I need to configure 2 different Queue:
@Configuration
public class DataQueue {
/**
* Queue to store the {@link SlowVars} read from the PLC
* @return the queue
*/
@Bean
Set<JsonNode> slowVarsQueue() {
return new CopyOnWriteArraySet<>();
}
/**
* Queue to store the {@link JsonNode} events
* @return the queue
*/
@Bean
Set<JsonNode> eventsQueue() {
return new CopyOnWriteArraySet<>();
}
}
In the class where I need to use one queue I wrote:
@Log4j2
@RestController
@AllArgsConstructor
public class EventsApiImpl implements EventsApi {
private Set<JsonNode> eventsQueue;
private MariaEventService mariaEventService;
private EventManipulationService eventManipulationService;
private ObjectMapper mapper;
// lots of methods
}
unfortunately Springs raise an error starting:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean
with name 'EventsApiImpl': Unsatisfied dependency expressed through constructor parameter 0:
No qualifying bean of type 'java.util.Set[com.fasterxml.jackson.databind.JsonNode]' available:
expected single matching bean but found 2: slowVarsQueue,eventsQueue
The two beans has different names one is slowVarsQueue
and the other is eventsQueue
, why Spring complain ?
As your error states, spring can't decide on which bean to use. Both of your beans provide the same datatype, so you have to define which one should be used by spring.
The easiest way is to just add @Primary
to the bean you want to use. You could also use @Qualifier
, to select specifically which bean you want to use.
@Qualifier
: https://www.baeldung.com/spring-bean-names@Primary
for one of the beans@Configuration
public class DataQueue {
@Bean
@Primary
Set<JsonNode> slowVarsQueue() {
return new CopyOnWriteArraySet<>();
}
@Bean
Set<JsonNode> eventsQueue() {
return new CopyOnWriteArraySet<>();
}
}
This solution limits your use of the eventsQueue()
bean, meaning it will only be accessible by name. Any code that requires a bean of type Set<JsonNode>
, and does not specify the name of the bean, will use slowVarsQueue()
. If you want to use eventsQueue()
at a specific location, you would need to reference it specifically by name with @Qualifier
or other means of referencing beans by name.
@Qualifier
in the constructor arguments@Log4j2
@RestController
public class EventsApiImpl implements EventsApi {
private Set<JsonNode> eventsQueue;
private MariaEventService mariaEventService;
private EventManipulationService eventManipulationService;
private ObjectMapper mapper;
public EventsApiImpl(@Qualifier("eventsQueue") Set<JsonNode> eventsQueue, MariaEventService mariaEventService, EventManipulationService eventManipulationService, ObjectMapper mapper)
{
this.eventsQueue = eventsQueue;
this.mariaEventService = mariaEventService;
this.eventManipulationService = eventManipulationService;
this.mapper = mapper;
}
// lots of methods
}
This option still keeps both beans as equally valuable, meaning other parts of your code will still throw the exception if they try to access a bean of type Set<JsonNode>
. This has the advantage, that you always know which bean you access. But if you access these beans in many locations in your code, you have to define the @Qualifier
in each of these locations.
@Primary
AND @Qualifier
Use both of the code-snippets from 1) and 2) for this solution. This has the advantage, that whenever you access a Set<JsonNode>
bean anywhere in your code, it will always use slowVarsQueue()
without throwing an exception. But if you want to use eventsQueue()
in any of those places, you can simply do so, by using @Qualifier("eventsQueue")
.