Letting Quarkus Inject All Beans of a Type with the @All Annotation
When letting a CDI container inject beans e.g. by using @Inject annotated members in a class, the container usually tries to find any matching bean and injects it. Sometimes it can be helpful to let the CDI container inject all beans of a type. Consider the following scenario: We want to process incoming events by different Processor classes depending on the type of the incoming event.
1public interface Event {
2 String getPayload();
3}
4
5@Data
6public class EventA implements Event {
7 private String payload;
8}
9
10public interface Processor {
11 String process(final Event event);
12}
13
14@ApplicationScoped
15public class ProcessorA implements Processor {
16 @Override
17 public String process(final Event event) {
18 return "Greetings from ProcessorA!";
19 }
20}
Beginners would intuitively start with a big if .. else if .. statement that distinguishes all the different events and forwards them to the corresponding processors. Apart from not being very elegant this is cumbersome when event types are added or removed later. It would be much smarter to have a mechanism which automatically recognizes added or removed processors. One way to achieve this is the classic "Replace Conditional with Polymorphism"-approach. Here we follow a different one, namely via the Quarkus annotation @All which allows injecting all disambiguated beans of a type. For that we implement a class EventProcessorMapper like so
1@ApplicationScoped
2public class EventProcessorMapper {
3 @Inject
4 @All
5 List<Processor> processors;
6
7 public Processor fromClass(final Class<? extends Event> clazz) {
8 return processors.stream()
9 .filter(processor -> processor.getSupportedEventClass().equals(clazz))
10 .findFirst()
11 .orElseThrow();
12 }
13}
The @Inject annotation together with the @All annotation tells Quarkus to inject all beans of the type Processor into the annotated List<Processor>. Since every implementation of the Processor interface is @ApplicationScoped annotated, all currently existing processors are injected. When a new implementation of Processor is added, the CDI container injects it together with the existing ones into the list, and likewise when an implementation is removed.
The actual mapping between Event and Processor is done in the fromClass method in this example. This method receives the Class of an Event, iterates over all the injected Processor beans and tries to match it via the getSupportedEventClass() method which we add to the Processor interface and subsequently to all implementations, each returning the corresponding Event type for which the concrete processor is responsible.
1public interface Processor {
2 Class<? extends Event> getSupportedEventClass();
3 String process(final Event event);
4}
5
6@ApplicationScoped
7public class ProcessorA implements Processor {
8 @Override
9 public Class<? extends Event> getSupportedEventClass() {
10 return EventA.class;
11 }
12
13 @Override
14 public String process(final Event event) {
15 return "Greetings from ProcessorA!";
16 }
17}
In this sample scenario all incoming events are handled by a GreetingService which retrieves the corresponding Processor via the EventProcessorMapper and calls the process method on it to let the processor do its work.
1@ApplicationScoped
2@RequiredArgsConstructor
3public class GreetingService {
4 private final EventProcessorMapper eventProcessorMapper;
5
6 public String processEvent(final Event event) {
7 final Processor processor = eventProcessorMapper.fromClass(event.getClass());
8 return processor.process(event);
9 }
10}
By using the @All annotation to inject all beans of a type we delegate the work of keeping track of currently existing implementations to Quarkus. New implementations can easily be added or existing ones removed without having to adapt conditional statements. Quarkus always injects the set of Processor implementations which are currently registered and the EventProcessorMapper takes care of picking the right processor when an event arrives.
A fully working native-compilation ready sample project with a simple REST ressource for receiving JSON events and some sample events and processors can be found on my GitLab instance.