How do I get a reference of all beans implementing a specific generic interface (e.g. Filter<TestEvent
>) in Spring?
This is what I want to achieve with a minimum number of lines:
public interface Filter<T extends Event> {
boolean approve(T event);
}
public class TestEventFilter implements Filter<TestEvent> {
public boolean approve(TestEvent event){
return false;
}
}
public class EventHandler{
private ApplicationContext context;
public void Eventhandler(DomainEvent event) {
// I want to do something like following, but this is not valid code
Map<String, Filter> filters = context.getBeansOfType(Filter<event.getClass()>.class);
for(Filter filter: filters.values()){
if (!filter.approve(event)) {
return; // abort if a filter does not approve the event
}
}
//...
}
}
My current implementation uses reflection to determine if filter.approve does accept the event before calling it. E.g.
Map<String, Filter> filters = context.getBeansOfType(Filter.class);
for(Filter filter: filters.values()){
if (doesFilterAcceptEventAsArgument(filter, event)) {
if (!filter.approve(event)) {
return; // abort if a filter does not approve the event
}
}
}
Where the doesFilterAcceptEventAsArgument does all the ugly work that I would like would like to get away from. Any suggestions?
In Spring Boot, you can use appContext. getBeanDefinitionNames() to get all the beans loaded by the Spring container.
The Spring container recognizes that LocalSessionFactoryBean implements the FactoryBean interface, and thus treats this bean specially: An instance of LocalSessionFactoryBean is instantiated, but instead of being directly returned, instead the getObject() method is invoked.
Ways to get loaded beans in Spring / Spring boot ApplicationContext. getBeanDefinitionNames() will return names of beans which is correctly loaded. getBean(String name) method using that we can get particular bean using bean name.
Just for reference, the simplest solution I could construct was this:
Map<String, Filter> filters = context.getBeansOfType(Filter.class);
for(Filter filter: filters.values()){
try {
if (!filter.approve(event)) {
return; // abort if a filter does not approve the event.
}
} catch (ClassCastException ignored){ }
}
And it worked quite well for prototyping.
If your question is "does Spring have a nicer way to do this", then the answer is "no". Hence, your method looks like the ubiquitous way to achieve this (get all beans of the raw class, then use reflection to look up the generic bound and compare it with the target's class).
In general, using generic information at runtime is tricky if possible at all. In this case you can get the generic bounds, but you're not really getting much benefit from the generic definition itself, other than using it as a form of annotation to check manually.
In any case, you will have to perform some kind of check on the returned object, so your original code block isn't going to work; the only variation is in the implementation of doesFilterAcceptEventAsArgument
. The classic OO way, would be to add an abstract superclass with two methods as follows (and add the latter to the Filter interface):
protected abstract Class<E> getEventClass();
public boolean acceptsEvent(Object event) // or an appropriate class for event
{
return getEventClass().isAssignableFrom(event.getClass());
}
This is kind of a pain because you'll have to implement the trivial getEventClass()
methods in every implementation to return the appropriate class literal, but it's a known limitation of generics. Within the bounds of the language, this is likely the cleanest approach.
But yours is fine for what it's worth.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With