Add ResolvableTypeProvider
Provide a mean to detect the actual ResolvableType based on a instance as a counter measure to type erasure. Upgrade the event infrastructure to detect if the event (or the payload) implements such interface. When this is the case, the return value of `getResolvableType` is used to validate its generic type against the method signature of the listener. Issue: SPR-13069
This commit is contained in:
@@ -21,6 +21,7 @@ import java.io.IOException;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.ResolvableTypeProvider;
|
||||
|
||||
/**
|
||||
* @author Stephane Nicoll
|
||||
@@ -53,6 +54,23 @@ public abstract class AbstractApplicationEventListenerTests {
|
||||
|
||||
}
|
||||
|
||||
protected static class SmartGenericTestEvent<T>
|
||||
extends GenericTestEvent<T> implements ResolvableTypeProvider {
|
||||
|
||||
private final ResolvableType resolvableType;
|
||||
|
||||
public SmartGenericTestEvent(Object source, T payload) {
|
||||
super(source, payload);
|
||||
this.resolvableType = ResolvableType.forClassWithGenerics(
|
||||
getClass(), payload.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResolvableType getResolvableType() {
|
||||
return this.resolvableType;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class StringEvent extends GenericTestEvent<String> {
|
||||
|
||||
public StringEvent(Object source, String payload) {
|
||||
|
||||
@@ -75,7 +75,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
|
||||
getGenericApplicationEventType("longEvent"));
|
||||
}
|
||||
|
||||
@Test // Unfortunate - this should work as well
|
||||
@Test
|
||||
public void multicastGenericEventWildcardSubType() {
|
||||
multicastEvent(false, StringEventListener.class, createGenericTestEvent("test"),
|
||||
getGenericApplicationEventType("wildcardEvent"));
|
||||
@@ -91,6 +91,16 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
|
||||
multicastEvent(false, StringEventListener.class, new LongEvent(this, 123L), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multicastSmartGenericTypeGenericListener() {
|
||||
multicastEvent(true, StringEventListener.class, new SmartGenericTestEvent<>(this, "test"), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multicastSmartGenericWrongTypeGenericListener() {
|
||||
multicastEvent(false, StringEventListener.class, new SmartGenericTestEvent<>(this, 123L), null);
|
||||
}
|
||||
|
||||
private void multicastEvent(boolean match, Class<?> listenerType,
|
||||
ApplicationEvent event, ResolvableType eventType) {
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.PayloadApplicationEvent;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.ResolvableTypeProvider;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
@@ -155,6 +156,42 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
|
||||
verify(this.sampleEvents, times(1)).handleGenericString(event);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeListenerWithGenericEvent() {
|
||||
Method method = ReflectionUtils.findMethod(SampleEvents.class,
|
||||
"handleGenericString", GenericTestEvent.class);
|
||||
GenericTestEvent<String> event = new SmartGenericTestEvent<>(this, "test");
|
||||
invokeListener(method, event);
|
||||
verify(this.sampleEvents, times(1)).handleGenericString(event);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeListenerWithGenericPayload() {
|
||||
Method method = ReflectionUtils.findMethod(SampleEvents.class,
|
||||
"handleGenericStringPayload", EntityWrapper.class);
|
||||
EntityWrapper<String> payload = new EntityWrapper<>("test");
|
||||
invokeListener(method, new PayloadApplicationEvent<>(this, payload));
|
||||
verify(this.sampleEvents, times(1)).handleGenericStringPayload(payload);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeListenerWithWrongGenericPayload() {
|
||||
Method method = ReflectionUtils.findMethod(SampleEvents.class,
|
||||
"handleGenericStringPayload", EntityWrapper.class);
|
||||
EntityWrapper<Integer> payload = new EntityWrapper<>(123);
|
||||
invokeListener(method, new PayloadApplicationEvent<>(this, payload));
|
||||
verify(this.sampleEvents, times(0)).handleGenericStringPayload(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeListenerWithAnyGenericPayload() {
|
||||
Method method = ReflectionUtils.findMethod(SampleEvents.class,
|
||||
"handleGenericAnyPayload", EntityWrapper.class);
|
||||
EntityWrapper<String> payload = new EntityWrapper<>("test");
|
||||
invokeListener(method, new PayloadApplicationEvent<>(this, payload));
|
||||
verify(this.sampleEvents, times(1)).handleGenericAnyPayload(payload);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeListenerRuntimeException() {
|
||||
Method method = ReflectionUtils.findMethod(SampleEvents.class,
|
||||
@@ -284,6 +321,16 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
|
||||
public void handleString(String payload) {
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void handleGenericStringPayload(EntityWrapper<String> event) {
|
||||
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void handleGenericAnyPayload(EntityWrapper<?> event) {
|
||||
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void tooManyParameters(String event, String whatIsThis) {
|
||||
}
|
||||
@@ -313,6 +360,19 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
|
||||
|
||||
}
|
||||
|
||||
private static class EntityWrapper<T> implements ResolvableTypeProvider {
|
||||
private final T entity;
|
||||
|
||||
public EntityWrapper(T entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResolvableType getResolvableType() {
|
||||
return ResolvableType.forClassWithGenerics(getClass(), this.entity.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
static class InvalidProxyTestBean implements SimpleService {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -66,6 +66,12 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
|
||||
supportsEventType(true, StringEventListener.class, eventType);
|
||||
}
|
||||
|
||||
@Test // or if the event provides its precise type
|
||||
public void genericListenerStrictTypeAndResolvableTypeProvider() {
|
||||
ResolvableType eventType = new SmartGenericTestEvent<>(this, "foo").getResolvableType();
|
||||
supportsEventType(true, StringEventListener.class, eventType);
|
||||
}
|
||||
|
||||
@Test // Demonstrates it works if we actually use the subtype
|
||||
public void genericListenerStrictTypeEventSubType() {
|
||||
StringEvent stringEvent = new StringEvent(this, "test");
|
||||
|
||||
Reference in New Issue
Block a user