mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-05-02 20:09:31 +01:00
Fixed event listener caching through equals/hashCode on SyntheticParameterizedType
Issue: SPR-13540
This commit is contained in:
+4
-4
@@ -60,7 +60,7 @@ public abstract class AbstractApplicationEventMulticaster
|
||||
|
||||
private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);
|
||||
|
||||
private final Map<ListenerCacheKey, ListenerRetriever> retrieverCache =
|
||||
final Map<ListenerCacheKey, ListenerRetriever> retrieverCache =
|
||||
new ConcurrentHashMap<ListenerCacheKey, ListenerRetriever>(64);
|
||||
|
||||
private ClassLoader beanClassLoader;
|
||||
@@ -303,13 +303,13 @@ public abstract class AbstractApplicationEventMulticaster
|
||||
return true;
|
||||
}
|
||||
ListenerCacheKey otherKey = (ListenerCacheKey) other;
|
||||
return ObjectUtils.nullSafeEquals(this.eventType, otherKey.eventType) &&
|
||||
ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType);
|
||||
return (ObjectUtils.nullSafeEquals(this.eventType, otherKey.eventType) &&
|
||||
ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return ObjectUtils.nullSafeHashCode(this.eventType) * 29 + ObjectUtils.nullSafeHashCode(this.sourceType);
|
||||
return (ObjectUtils.nullSafeHashCode(this.eventType) * 29 + ObjectUtils.nullSafeHashCode(this.sourceType));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+41
-4
@@ -34,6 +34,7 @@ import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.BeanThatBroadcasts;
|
||||
import org.springframework.context.BeanThatListens;
|
||||
import org.springframework.context.PayloadApplicationEvent;
|
||||
import org.springframework.context.support.AbstractApplicationContext;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
@@ -101,8 +102,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
|
||||
multicastEvent(false, StringEventListener.class, new SmartGenericTestEvent<>(this, 123L), null);
|
||||
}
|
||||
|
||||
private void multicastEvent(boolean match, Class<?> listenerType,
|
||||
ApplicationEvent event, ResolvableType eventType) {
|
||||
private void multicastEvent(boolean match, Class<?> listenerType, ApplicationEvent event, ResolvableType eventType) {
|
||||
@SuppressWarnings("unchecked")
|
||||
ApplicationListener<ApplicationEvent> listener =
|
||||
(ApplicationListener<ApplicationEvent>) mock(listenerType);
|
||||
@@ -111,7 +111,8 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
|
||||
|
||||
if (eventType != null) {
|
||||
smc.multicastEvent(event, eventType);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
smc.multicastEvent(event);
|
||||
}
|
||||
int invocation = match ? 1 : 0;
|
||||
@@ -267,6 +268,31 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
|
||||
assertTrue(listener1.seenEvents.contains(event3));
|
||||
assertTrue(listener1.seenEvents.contains(event4));
|
||||
|
||||
AbstractApplicationEventMulticaster multicaster = context.getBean(AbstractApplicationEventMulticaster.class);
|
||||
assertEquals(2, multicaster.retrieverCache.size());
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listenersInApplicationContextWithPayloadEvents() {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.registerBeanDefinition("listener", new RootBeanDefinition(MyPayloadListener.class));
|
||||
context.refresh();
|
||||
|
||||
MyPayloadListener listener = context.getBean("listener", MyPayloadListener.class);
|
||||
context.publishEvent("event1");
|
||||
context.publishEvent("event2");
|
||||
context.publishEvent("event3");
|
||||
context.publishEvent("event4");
|
||||
assertTrue(listener.seenPayloads.contains("event1"));
|
||||
assertTrue(listener.seenPayloads.contains("event2"));
|
||||
assertTrue(listener.seenPayloads.contains("event3"));
|
||||
assertTrue(listener.seenPayloads.contains("event4"));
|
||||
|
||||
AbstractApplicationEventMulticaster multicaster = context.getBean(AbstractApplicationEventMulticaster.class);
|
||||
assertEquals(2, multicaster.retrieverCache.size());
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -278,7 +304,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
|
||||
nestedChild.setInitMethodName("refresh");
|
||||
context.registerBeanDefinition("nestedChild", nestedChild);
|
||||
RootBeanDefinition listener1Def = new RootBeanDefinition(MyOrderedListener1.class);
|
||||
listener1Def.setDependsOn(new String[] {"nestedChild"});
|
||||
listener1Def.setDependsOn("nestedChild");
|
||||
context.registerBeanDefinition("listener1", listener1Def);
|
||||
context.refresh();
|
||||
|
||||
@@ -431,6 +457,17 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
|
||||
}
|
||||
|
||||
|
||||
public static class MyPayloadListener implements ApplicationListener<PayloadApplicationEvent> {
|
||||
|
||||
public final Set<Object> seenPayloads = new HashSet<Object>();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(PayloadApplicationEvent event) {
|
||||
this.seenPayloads.add(event.getPayload());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class MyNonSingletonListener implements ApplicationListener<ApplicationEvent> {
|
||||
|
||||
public static final Set<ApplicationEvent> seenEvents = new HashSet<ApplicationEvent>();
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
@@ -1393,8 +1394,8 @@ public class ResolvableType implements Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type[] getActualTypeArguments() {
|
||||
return this.typeArguments;
|
||||
public Type getOwnerType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1403,8 +1404,26 @@ public class ResolvableType implements Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getOwnerType() {
|
||||
return null;
|
||||
public Type[] getActualTypeArguments() {
|
||||
return this.typeArguments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof ParameterizedType)) {
|
||||
return false;
|
||||
}
|
||||
ParameterizedType otherType = (ParameterizedType) other;
|
||||
return (otherType.getOwnerType() == null && this.rawType.equals(otherType.getRawType()) &&
|
||||
Arrays.equals(this.typeArguments, otherType.getActualTypeArguments()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (this.rawType.hashCode() * 31 + Arrays.hashCode(this.typeArguments));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@ package org.springframework.core;
|
||||
|
||||
/**
|
||||
* Any object can implement this interface to provide its actual {@link ResolvableType}.
|
||||
* <p>
|
||||
* Such information is very useful when figuring out if the instance matches a generic
|
||||
*
|
||||
* <p>Such information is very useful when figuring out if the instance matches a generic
|
||||
* signature as Java does not convey the signature at runtime.
|
||||
* <p>
|
||||
* Users of this interface should be careful in complex hierarchy scenarios, especially
|
||||
*
|
||||
* <p>Users of this interface should be careful in complex hierarchy scenarios, especially
|
||||
* when the generic type signature of the class changes in sub-classes. It is always
|
||||
* possible to return {@code null} to fallback on a default behaviour.
|
||||
*
|
||||
@@ -32,8 +32,8 @@ package org.springframework.core;
|
||||
public interface ResolvableTypeProvider {
|
||||
|
||||
/**
|
||||
* Return the {@link ResolvableType} describing this instance or {@code null} if some
|
||||
* sort of default should be applied instead.
|
||||
* Return the {@link ResolvableType} describing this instance
|
||||
* (or {@code null} if some sort of default should be applied instead).
|
||||
*/
|
||||
ResolvableType getResolvableType();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user