Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get all beans implementing a generic interface in Spring

Tags:

java

spring

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?

like image 715
Ola Herrdahl Avatar asked Jul 12 '10 12:07

Ola Herrdahl


People also ask

How do you get all the beans in your Spring boot application?

In Spring Boot, you can use appContext. getBeanDefinitionNames() to get all the beans loaded by the Spring container.

Which interface does Spring bean implement?

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.

How do you get beans from Spring containers?

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.


2 Answers

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.

like image 145
Ola Herrdahl Avatar answered Sep 21 '22 17:09

Ola Herrdahl


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.

like image 28
Andrzej Doyle Avatar answered Sep 22 '22 17:09

Andrzej Doyle