Browse Source
Update the event publishing infrastructure to support generics-based events, that is support ApplicationListener implementations that define a generic event, something like: public class MyListener implements ApplicationListener<GenericEvent<String>> { ... } This listener should only receive events that are matching the generic signature, for instance: public class StringEvent extends GenericEvent<String> { ... } Note that because of type erasure, publishing an event that defines the generic type at the instance level will not work. In other words, publishing "new GenericEvent<String>" will not work as expected as type erasure will define it as GenericEvent<?>. To support this feature, use the new GenericApplicationListener that supersedes SmartApplicationListener to handle generics-based even types via `supportsEventType` that takes a ResolvableType instance instead of the simple Class of the event. ApplicationEventMulticaster has an additional method to multicast an event based on the event and its ResolvableType. Issue: SPR-8201pull/723/merge
Stephane Nicoll
10 years ago
13 changed files with 507 additions and 68 deletions
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
/* |
||||
* Copyright 2002-2015 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.context.event; |
||||
|
||||
import org.springframework.context.ApplicationEvent; |
||||
import org.springframework.context.ApplicationListener; |
||||
import org.springframework.core.Ordered; |
||||
import org.springframework.core.ResolvableType; |
||||
|
||||
/** |
||||
* Extended variant of the standard {@link ApplicationListener} interface, |
||||
* exposing further metadata such as the supported event type. |
||||
* |
||||
* <p>As of Spring Framework 4.2, supersedes {@link SmartApplicationListener} with |
||||
* proper handling of generics-based event. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 4.2 |
||||
*/ |
||||
public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered { |
||||
|
||||
/** |
||||
* Determine whether this listener actually supports the given event type. |
||||
*/ |
||||
boolean supportsEventType(ResolvableType eventType); |
||||
|
||||
/** |
||||
* Determine whether this listener actually supports the given source type. |
||||
*/ |
||||
boolean supportsSourceType(Class<?> sourceType); |
||||
|
||||
} |
@ -0,0 +1,129 @@
@@ -0,0 +1,129 @@
|
||||
/* |
||||
* Copyright 2002-2015 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.context.event; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.springframework.context.ApplicationEvent; |
||||
import org.springframework.context.ApplicationListener; |
||||
import org.springframework.core.ResolvableType; |
||||
|
||||
/** |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
@SuppressWarnings("serial") |
||||
public abstract class AbstractApplicationEventListenerTests { |
||||
|
||||
protected ResolvableType getGenericApplicationEventType(String fieldName) { |
||||
try { |
||||
return ResolvableType.forField(GenericApplicationEvents.class.getField(fieldName)); |
||||
} |
||||
catch (NoSuchFieldException e) { |
||||
throw new IllegalStateException("No such field on Events '" + fieldName + "'"); |
||||
} |
||||
} |
||||
|
||||
protected static class GenericApplicationEvent<T> |
||||
extends ApplicationEvent { |
||||
|
||||
private final T payload; |
||||
|
||||
public GenericApplicationEvent(Object source, T payload) { |
||||
super(source); |
||||
this.payload = payload; |
||||
} |
||||
|
||||
public T getPayload() { |
||||
return payload; |
||||
} |
||||
|
||||
} |
||||
|
||||
protected static class StringEvent extends GenericApplicationEvent<String> { |
||||
|
||||
public StringEvent(Object source, String payload) { |
||||
super(source, payload); |
||||
} |
||||
} |
||||
|
||||
protected static class LongEvent extends GenericApplicationEvent<Long> { |
||||
|
||||
public LongEvent(Object source, Long payload) { |
||||
super(source, payload); |
||||
} |
||||
} |
||||
|
||||
protected <T> GenericApplicationEvent<T> createGenericEvent(T payload) { |
||||
return new GenericApplicationEvent<>(this, payload); |
||||
} |
||||
|
||||
|
||||
static class GenericEventListener implements ApplicationListener<GenericApplicationEvent<?>> { |
||||
@Override |
||||
public void onApplicationEvent(GenericApplicationEvent<?> event) { |
||||
|
||||
} |
||||
} |
||||
|
||||
static class ObjectEventListener implements ApplicationListener<GenericApplicationEvent<Object>> { |
||||
@Override |
||||
public void onApplicationEvent(GenericApplicationEvent<Object> event) { |
||||
|
||||
} |
||||
} |
||||
|
||||
static class UpperBoundEventListener |
||||
implements ApplicationListener<GenericApplicationEvent<? extends RuntimeException>> { |
||||
|
||||
@Override |
||||
public void onApplicationEvent(GenericApplicationEvent<? extends RuntimeException> event) { |
||||
|
||||
} |
||||
} |
||||
|
||||
static class StringEventListener implements ApplicationListener<GenericApplicationEvent<String>> { |
||||
|
||||
@Override |
||||
public void onApplicationEvent(GenericApplicationEvent<String> event) { |
||||
|
||||
} |
||||
} |
||||
|
||||
static class RawApplicationListener implements ApplicationListener { |
||||
@Override |
||||
public void onApplicationEvent(ApplicationEvent event) { |
||||
|
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("unused") |
||||
static class GenericApplicationEvents { |
||||
|
||||
public GenericApplicationEvent<?> wildcardEvent; |
||||
|
||||
public GenericApplicationEvent<String> stringEvent; |
||||
|
||||
public GenericApplicationEvent<Long> longEvent; |
||||
|
||||
public GenericApplicationEvent<IllegalStateException> illegalStateExceptionEvent; |
||||
|
||||
public GenericApplicationEvent<IOException> ioExceptionEvent; |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,148 @@
@@ -0,0 +1,148 @@
|
||||
/* |
||||
* Copyright 2002-2015 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.context.event; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
import org.springframework.context.ApplicationEvent; |
||||
import org.springframework.context.ApplicationListener; |
||||
import org.springframework.core.ResolvableType; |
||||
|
||||
import static org.junit.Assert.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
/** |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
public class GenericApplicationListenerAdapterTests extends AbstractApplicationEventListenerTests { |
||||
|
||||
@Test |
||||
public void supportsEventTypeWithSmartApplicationListener() { |
||||
SmartApplicationListener smartListener = mock(SmartApplicationListener.class); |
||||
GenericApplicationListenerAdapter listener = new GenericApplicationListenerAdapter(smartListener); |
||||
ResolvableType type = ResolvableType.forClass(ApplicationEvent.class); |
||||
listener.supportsEventType(type); |
||||
verify(smartListener, times(1)).supportsEventType(ApplicationEvent.class); |
||||
} |
||||
|
||||
@Test |
||||
public void supportsSourceTypeWithSmartApplicationListener() { |
||||
SmartApplicationListener smartListener = mock(SmartApplicationListener.class); |
||||
GenericApplicationListenerAdapter listener = new GenericApplicationListenerAdapter(smartListener); |
||||
listener.supportsSourceType(Object.class); |
||||
verify(smartListener, times(1)).supportsSourceType(Object.class); |
||||
} |
||||
|
||||
@Test |
||||
public void genericListenerStrictType() { |
||||
supportsEventType(true, StringEventListener.class, getGenericApplicationEventType("stringEvent")); |
||||
} |
||||
|
||||
@Test // Demonstrates we can't inject that event because the generic type is lost
|
||||
public void genericListenerStrictTypeTypeErasure() { |
||||
GenericApplicationEvent<String> stringEvent = createGenericEvent("test"); |
||||
ResolvableType eventType = ResolvableType.forType(stringEvent.getClass()); |
||||
supportsEventType(false, StringEventListener.class, eventType); |
||||
} |
||||
|
||||
@Test // But it works if we specify the type properly
|
||||
public void genericListenerStrictTypeAndResolvableType() { |
||||
ResolvableType eventType = ResolvableType |
||||
.forClassWithGenerics(GenericApplicationEvent.class, String.class); |
||||
supportsEventType(true, StringEventListener.class, eventType); |
||||
} |
||||
|
||||
@Test // Demonstrates it works if we actually use the subtype
|
||||
public void genericListenerStrictTypeEventSubType() { |
||||
StringEvent stringEvent = new StringEvent(this, "test"); |
||||
ResolvableType eventType = ResolvableType.forType(stringEvent.getClass()); |
||||
supportsEventType(true, StringEventListener.class, eventType); |
||||
} |
||||
|
||||
@Test |
||||
public void genericListenerStrictTypeNotMatching() { |
||||
supportsEventType(false, StringEventListener.class, getGenericApplicationEventType("longEvent")); |
||||
} |
||||
|
||||
@Test |
||||
public void genericListenerStrictTypeEventSubTypeNotMatching() { |
||||
LongEvent stringEvent = new LongEvent(this, 123L); |
||||
ResolvableType eventType = ResolvableType.forType(stringEvent.getClass()); |
||||
supportsEventType(false, StringEventListener.class, eventType); |
||||
} |
||||
|
||||
@Test |
||||
public void genericListenerStrictTypeNotMatchTypeErasure() { |
||||
GenericApplicationEvent<Long> longEvent = createGenericEvent(123L); |
||||
ResolvableType eventType = ResolvableType.forType(longEvent.getClass()); |
||||
supportsEventType(false, StringEventListener.class, eventType); |
||||
} |
||||
|
||||
@Test |
||||
public void genericListenerStrictTypeSubClass() { |
||||
supportsEventType(false, ObjectEventListener.class, |
||||
getGenericApplicationEventType("longEvent")); |
||||
} |
||||
|
||||
@Test |
||||
public void genericListenerUpperBoundType() { |
||||
supportsEventType(true, UpperBoundEventListener.class, |
||||
getGenericApplicationEventType("illegalStateExceptionEvent")); |
||||
} |
||||
|
||||
@Test |
||||
public void genericListenerUpperBoundTypeNotMatching() throws NoSuchFieldException { |
||||
supportsEventType(false, UpperBoundEventListener.class, |
||||
getGenericApplicationEventType("ioExceptionEvent")); |
||||
} |
||||
|
||||
@Test |
||||
public void genericListenerWildcardType() { |
||||
supportsEventType(true, GenericEventListener.class, |
||||
getGenericApplicationEventType("stringEvent")); |
||||
} |
||||
|
||||
@Test // Demonstrates we cant inject that event because the listener has a wildcard
|
||||
public void genericListenerWildcardTypeTypeErasure() { |
||||
GenericApplicationEvent<String> stringEvent = createGenericEvent("test"); |
||||
ResolvableType eventType = ResolvableType.forType(stringEvent.getClass()); |
||||
supportsEventType(true, GenericEventListener.class, eventType); |
||||
} |
||||
|
||||
@Test |
||||
public void genericListenerRawType() { |
||||
supportsEventType(true, RawApplicationListener.class, |
||||
getGenericApplicationEventType("stringEvent")); |
||||
} |
||||
|
||||
@Test // Demonstrates we cant inject that event because the listener has a raw type
|
||||
public void genericListenerRawTypeTypeErasure() { |
||||
GenericApplicationEvent<String> stringEvent = createGenericEvent("test"); |
||||
ResolvableType eventType = ResolvableType.forType(stringEvent.getClass()); |
||||
supportsEventType(true, RawApplicationListener.class, eventType); |
||||
} |
||||
|
||||
private void supportsEventType(boolean match, Class<? extends ApplicationListener> listenerType, |
||||
ResolvableType eventType) { |
||||
|
||||
ApplicationListener<?> listener = mock(listenerType); |
||||
GenericApplicationListenerAdapter adapter = new GenericApplicationListenerAdapter(listener); |
||||
assertEquals("Wrong match for event '" + eventType + "' on " + listenerType.getClass().getName(), |
||||
match, adapter.supportsEventType(eventType)); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue