From 427767f21e7c6c2525501ae110ca483b2d52806f Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 6 Oct 2015 00:05:51 +0200 Subject: [PATCH] Fixed event listener caching through equals/hashCode on SyntheticParameterizedType Issue: SPR-13540 --- .../AbstractApplicationEventMulticaster.java | 8 ++-- .../event/ApplicationContextEventTests.java | 45 +++++++++++++++++-- .../springframework/core/ResolvableType.java | 27 +++++++++-- .../core/ResolvableTypeProvider.java | 12 ++--- 4 files changed, 74 insertions(+), 18 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java index cf1ed74539..0eb68e9e5f 100644 --- a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java +++ b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java @@ -60,7 +60,7 @@ public abstract class AbstractApplicationEventMulticaster private final ListenerRetriever defaultRetriever = new ListenerRetriever(false); - private final Map retrieverCache = + final Map retrieverCache = new ConcurrentHashMap(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)); } } diff --git a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java index d4f5445c2f..a56c77be07 100644 --- a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java @@ -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 listener = (ApplicationListener) 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 { + + public final Set seenPayloads = new HashSet(); + + @Override + public void onApplicationEvent(PayloadApplicationEvent event) { + this.seenPayloads.add(event.getPayload()); + } + } + + public static class MyNonSingletonListener implements ApplicationListener { public static final Set seenEvents = new HashSet(); diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index 07da8f214c..c25a61256f 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -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)); } } diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableTypeProvider.java b/spring-core/src/main/java/org/springframework/core/ResolvableTypeProvider.java index dc0490ade9..1f361fbc5d 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableTypeProvider.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableTypeProvider.java @@ -18,11 +18,11 @@ package org.springframework.core; /** * Any object can implement this interface to provide its actual {@link ResolvableType}. - *

- * Such information is very useful when figuring out if the instance matches a generic + * + *

Such information is very useful when figuring out if the instance matches a generic * signature as Java does not convey the signature at runtime. - *

- * Users of this interface should be careful in complex hierarchy scenarios, especially + * + *

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();