spring generic application event failing to reach destination

Viewed 3298

i have the following spring holder bean. the idea is it retains an instance of something and fires a change event when that something changes (supplying both the old and new values):

public class Holder<T> implements ApplicationEventPublisherAware {
    private final Class<T> payloadType; //because java generics are incomplete
    private T curr; //latest value being held
    private ApplicationEventPublisher publisher; //spring context ref

    public Holder(Class<T> payloadType) {
        this.payloadType = payloadType;
    }

    public void change() throws Exception {
        T prev = curr;
        curr = payloadType.newInstance();
        publisher.publishEvent(new ContentsChangedEvent<>(prev, curr));
    }
}

and then i have a listener class that reacts to those events:

@Named
public class Listener {

    @EventListener
    public void onChange(ContentsChangedEvent<Cat> event) {
        //never gets called
    }

    @EventListener
    public void onErasedChange(ContentsChangedEvent<?> event) {
        //does get called
    }
}

my issue is that the generic (correct) event listener method never gets invoked (the one that gets Event<Cat>), only the erased one.

for completeness' sake, here is the spring context.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="net.radai"/>

    <bean id="holder" class="net.radai.Holder">
        <constructor-arg type="java.lang.Class" value="net.radai.Cat"/>
    </bean>
</beans>

I've also put the whole thing on github - https://github.com/radai-rosenblatt/spring-events. its a maven project - you build it and you'll get a failing test.

what am i doing wrong?

NOTE: in the real use case for this i expect to have multiple such Holders and forcing all listeners to discard events not relevant to them in unacceptable

2 Answers

If you implement org.springframework.core.ResolvableTypeProvider in your generic event class, then listener will resolve it. As an example:

import org.springframework.core.ResolvableType;
import org.springframework.core.ResolvableTypeProvider;

public class EventBase<T> {
    public T payload;
}

public class AppEvent<T> extends EventBase<T> implements ResolvableTypeProvider {
    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(
            getClass(), 
            ResolvableType.forInstance(this.payload)
        );
    }
}

@Component
public class EventHandler {
    @EventListener
    public void handleString(AppEvent<String> event) {
       System.out.println("Event[" + event.getClass().getSimpleName() + "], received:" + event);
    }
    
    @EventListener
    public void handleLong(AppEvent<Long> event) {
       System.out.println("Event[" + event.getClass().getSimpleName() + "], received:" + event);
    }

}
Related