From 57874a6050d7291a9a7c74ef476f50ae7270dca5 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 17 Feb 2009 17:46:14 +0000 Subject: [PATCH] SmartApplicationListener interface supports source type checking; SimpleApplicationEventMulticaster caches information about event/source matches --- .../AbstractApplicationEventMulticaster.java | 157 ++++++++++++++++-- .../GenericApplicationListenerAdapter.java | 4 + .../SimpleApplicationEventMulticaster.java | 18 +- .../event/SmartApplicationListener.java | 5 + .../event/SourceFilteringListener.java | 4 + .../event/ApplicationContextEventTests.java | 31 +++- .../springframework/core/OrderComparator.java | 38 ++++- 7 files changed, 228 insertions(+), 29 deletions(-) diff --git a/org.springframework.context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java b/org.springframework.context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java index 5eab8f7b86..acf3f1dcd8 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java +++ b/org.springframework.context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java @@ -17,13 +17,15 @@ package org.springframework.context.event; import java.util.Collection; -import java.util.Collections; import java.util.LinkedHashSet; import java.util.LinkedList; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.OrderComparator; @@ -50,44 +52,167 @@ import org.springframework.core.OrderComparator; public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanFactoryAware { - private final Set applicationListeners = new LinkedHashSet(); + private final ListenerRetriever defaultRetriever = new ListenerRetriever(); - private final Set applicationListenerBeans = new LinkedHashSet(); + private final Map retrieverCache = + new ConcurrentHashMap(); private BeanFactory beanFactory; public void addApplicationListener(ApplicationListener listener) { - this.applicationListeners.add(listener); + this.defaultRetriever.applicationListeners.add(listener); } public void addApplicationListenerBean(String listenerBeanName) { - this.applicationListenerBeans.add(listenerBeanName); + this.defaultRetriever.applicationListenerBeans.add(listenerBeanName); } public final void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } + private BeanFactory getBeanFactory() { + if (this.beanFactory == null) { + throw new IllegalStateException("ApplicationEventMulticaster cannot retrieve listener beans " + + "because it is not associated with a BeanFactory"); + } + return this.beanFactory; + } + + /** - * Return the current Collection of ApplicationListeners. + * Return a Collection containing all ApplicationListeners. * @return a Collection of ApplicationListeners * @see org.springframework.context.ApplicationListener */ protected Collection getApplicationListeners() { - LinkedList allListeners = - new LinkedList(this.applicationListeners); - if (!this.applicationListenerBeans.isEmpty()) { - if (this.beanFactory == null) { - throw new IllegalStateException("ApplicationEventMulticaster cannot retrieve listener beans " + - "because it is not associated with a BeanFactory: " + this.applicationListenerBeans); + return this.defaultRetriever.getApplicationListeners(); + } + + /** + * Return a Collection of ApplicationListeners matching the given + * event type. Non-matching listeners get excluded early. + * @param event the event to be propagated. Allows for excluding + * non-matching listeners early, based on cached matching information. + * @return a Collection of ApplicationListeners + * @see org.springframework.context.ApplicationListener + */ + protected Collection getApplicationListeners(ApplicationEvent event) { + Class eventType = event.getClass(); + Class sourceType = event.getSource().getClass(); + ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType); + ListenerRetriever retriever = this.retrieverCache.get(cacheKey); + if (retriever != null) { + return retriever.getApplicationListeners(); + } + else { + retriever = new ListenerRetriever(); + LinkedList allListeners = new LinkedList(); + for (ApplicationListener listener : this.defaultRetriever.applicationListeners) { + if (supportsEvent(listener, eventType, sourceType)) { + retriever.applicationListeners.add(listener); + allListeners.add(listener); + } + } + if (!this.defaultRetriever.applicationListenerBeans.isEmpty()) { + BeanFactory beanFactory = getBeanFactory(); + for (String listenerBeanName : this.defaultRetriever.applicationListenerBeans) { + ApplicationListener listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); + if (supportsEvent(listener, eventType, sourceType)) { + retriever.applicationListenerBeans.add(listenerBeanName); + allListeners.add(listener); + } + } + } + OrderComparator.sort(allListeners); + this.retrieverCache.put(cacheKey, retriever); + return allListeners; + } + } + + /** + * Determine whether the given listener supports the given event. + *

The default implementation detects the {@link SmartApplicationListener} + * interface. In case of a standard {@link ApplicationListener}, a + * {@link GenericApplicationListenerAdapter} will be used to introspect + * the generically declared type of the target listener. + * @param listener the target listener to check + * @param eventType the event type to check against + * @param sourceType the source type to check against + * @return whether the given listener should be included in the + * candidates for the given event type + */ + protected boolean supportsEvent( + ApplicationListener listener, Class eventType, Class sourceType) { + + SmartApplicationListener smartListener = (listener instanceof SmartApplicationListener ? + (SmartApplicationListener) listener : new GenericApplicationListenerAdapter(listener)); + return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType)); + } + + + /** + * Cache key for ListenerRetrievers, based on event type and source type. + */ + private static class ListenerCacheKey { + + private final Class eventType; + + private final Class sourceType; + + public ListenerCacheKey(Class eventType, Class sourceType) { + this.eventType = eventType; + this.sourceType = sourceType; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + ListenerCacheKey otherKey = (ListenerCacheKey) other; + return (this.eventType.equals(otherKey.eventType) && this.sourceType.equals(otherKey.sourceType)); + } + + @Override + public int hashCode() { + return this.eventType.hashCode() * 29 + this.sourceType.hashCode(); + } + } + + + /** + * Helper class that encapsulates a specific set of target listeners, + * allowing for efficient retrieval of pre-filtered listeners. + *

An instance of this helper gets cached per event type and source type. + */ + private class ListenerRetriever { + + public final Set applicationListeners; + + public final Set applicationListenerBeans; + + public ListenerRetriever() { + this.applicationListeners = new LinkedHashSet(); + this.applicationListenerBeans = new LinkedHashSet(); + } + + public Collection getApplicationListeners() { + LinkedList allListeners = new LinkedList(); + for (ApplicationListener listener : this.applicationListeners) { + allListeners.add(listener); } - for (String listenerBeanName : applicationListenerBeans) { - allListeners.add(this.beanFactory.getBean(listenerBeanName, ApplicationListener.class)); + if (!this.applicationListenerBeans.isEmpty()) { + BeanFactory beanFactory = getBeanFactory(); + for (String listenerBeanName : this.applicationListenerBeans) { + ApplicationListener listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); + allListeners.add(listener); + } } + OrderComparator.sort(allListeners); + return allListeners; } - Collections.sort(allListeners, new OrderComparator()); - return allListeners; } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java b/org.springframework.context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java index c3a27a872e..585afa6403 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java +++ b/org.springframework.context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java @@ -57,6 +57,10 @@ public class GenericApplicationListenerAdapter implements SmartApplicationListen return getGenericEventType(this.delegate.getClass()).isAssignableFrom(eventType); } + public boolean supportsSourceType(Class sourceType) { + return true; + } + public int getOrder() { return (this.delegate instanceof Ordered ? ((Ordered) this.delegate).getOrder() : Ordered.LOWEST_PRECEDENCE); } diff --git a/org.springframework.context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java b/org.springframework.context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java index 9e89a18f6c..1ca1235db1 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java +++ b/org.springframework.context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java @@ -21,7 +21,6 @@ import java.util.concurrent.Executor; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; -import org.springframework.core.task.SyncTaskExecutor; /** * Simple implementation of the {@link ApplicationEventMulticaster} interface. @@ -42,7 +41,7 @@ import org.springframework.core.task.SyncTaskExecutor; */ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster { - private Executor taskExecutor = new SyncTaskExecutor(); + private Executor taskExecutor; /** @@ -72,7 +71,7 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM * @see org.springframework.scheduling.timer.TimerTaskExecutor */ public void setTaskExecutor(Executor taskExecutor) { - this.taskExecutor = (taskExecutor != null ? taskExecutor : new SyncTaskExecutor()); + this.taskExecutor = taskExecutor; } /** @@ -83,18 +82,21 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM } + @SuppressWarnings("unchecked") public void multicastEvent(final ApplicationEvent event) { - for (final ApplicationListener listener : getApplicationListeners()) { - SmartApplicationListener smartListener = (listener instanceof SmartApplicationListener ? - (SmartApplicationListener) listener : new GenericApplicationListenerAdapter(listener)); - if (smartListener.supportsEventType(event.getClass())) { - getTaskExecutor().execute(new Runnable() { + for (final ApplicationListener listener : getApplicationListeners(event)) { + Executor executor = getTaskExecutor(); + if (executor != null) { + executor.execute(new Runnable() { @SuppressWarnings("unchecked") public void run() { listener.onApplicationEvent(event); } }); } + else { + listener.onApplicationEvent(event); + } } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/event/SmartApplicationListener.java b/org.springframework.context/src/main/java/org/springframework/context/event/SmartApplicationListener.java index e5932eef43..f0fff2741b 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/event/SmartApplicationListener.java +++ b/org.springframework.context/src/main/java/org/springframework/context/event/SmartApplicationListener.java @@ -34,4 +34,9 @@ public interface SmartApplicationListener extends ApplicationListener, Ordered { */ boolean supportsEventType(Class eventType); + /** + * Determine whether this listener actually supports the given source type. + */ + boolean supportsSourceType(Class sourceType); + } diff --git a/org.springframework.context/src/main/java/org/springframework/context/event/SourceFilteringListener.java b/org.springframework.context/src/main/java/org/springframework/context/event/SourceFilteringListener.java index 8a8b0bd0ca..3fe6babc3c 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/event/SourceFilteringListener.java +++ b/org.springframework.context/src/main/java/org/springframework/context/event/SourceFilteringListener.java @@ -73,6 +73,10 @@ public class SourceFilteringListener implements SmartApplicationListener { return (this.delegate == null || this.delegate.supportsEventType(eventType)); } + public boolean supportsSourceType(Class sourceType) { + return sourceType.isInstance(this.source); + } + public int getOrder() { return (this.delegate != null ? this.delegate.getOrder() : Ordered.LOWEST_PRECEDENCE); } diff --git a/org.springframework.context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java b/org.springframework.context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java index 38a02101e8..d70e10c9b7 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java @@ -22,8 +22,7 @@ import java.util.Set; import org.aopalliance.intercept.MethodInvocation; import org.easymock.EasyMock; import static org.easymock.EasyMock.*; -import org.junit.Assert; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import org.junit.Test; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -89,6 +88,30 @@ public class ApplicationContextEventTests { verify(invocation, ctx); } + @Test + public void listenersInApplicationContext() { + StaticApplicationContext context = new StaticApplicationContext(); + context.registerBeanDefinition("listener1", new RootBeanDefinition(MyOrderedListener1.class)); + RootBeanDefinition listener2 = new RootBeanDefinition(MyOrderedListener2.class); + listener2.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference("listener1")); + context.registerBeanDefinition("listener2", listener2); + context.refresh(); + + MyOrderedListener1 listener1 = context.getBean("listener1", MyOrderedListener1.class); + MyEvent event1 = new MyEvent(context); + context.publishEvent(event1); + MyOtherEvent event2 = new MyOtherEvent(context); + context.publishEvent(event2); + MyEvent event3 = new MyEvent(context); + context.publishEvent(event3); + MyOtherEvent event4 = new MyOtherEvent(context); + context.publishEvent(event4); + assertTrue(listener1.seenEvents.contains(event1)); + assertTrue(listener1.seenEvents.contains(event2)); + assertTrue(listener1.seenEvents.contains(event3)); + assertTrue(listener1.seenEvents.contains(event4)); + } + @Test public void listenerAndBroadcasterWithCircularReference() { StaticApplicationContext context = new StaticApplicationContext(); @@ -98,9 +121,9 @@ public class ApplicationContextEventTests { context.registerBeanDefinition("listener", listenerDef); context.refresh(); - BeanThatBroadcasts broadcaster = (BeanThatBroadcasts) context.getBean("broadcaster"); + BeanThatBroadcasts broadcaster = context.getBean("broadcaster", BeanThatBroadcasts.class); context.publishEvent(new MyEvent(context)); - Assert.assertEquals("The event was not received by the listener", 2, broadcaster.receivedCount); + assertEquals("The event was not received by the listener", 2, broadcaster.receivedCount); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/OrderComparator.java b/org.springframework.core/src/main/java/org/springframework/core/OrderComparator.java index c5266948cd..829ee4c275 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/OrderComparator.java +++ b/org.springframework.core/src/main/java/org/springframework/core/OrderComparator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -16,7 +16,10 @@ package org.springframework.core; +import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; +import java.util.List; /** * {@link Comparator} implementation for {@link Ordered} objects, @@ -34,6 +37,12 @@ import java.util.Comparator; */ public class OrderComparator implements Comparator { + /** + * Shared default instance of OrderComparator. + */ + public static OrderComparator INSTANCE = new OrderComparator(); + + public int compare(Object o1, Object o2) { boolean p1 = (o1 instanceof PriorityOrdered); boolean p2 = (o2 instanceof PriorityOrdered); @@ -61,4 +70,31 @@ public class OrderComparator implements Comparator { return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : Ordered.LOWEST_PRECEDENCE); } + + /** + * Sort the given List with a default OrderComparator. + *

Optimized to skip sorting for lists with size 0 or 1, + * in order to avoid unnecessary array extraction. + * @param list the List to sort + * @see java.util.Collections#sort(java.util.List, java.util.Comparator) + */ + public static void sort(List list) { + if (list.size() > 1) { + Collections.sort(list, INSTANCE); + } + } + + /** + * Sort the given array with a default OrderComparator. + *

Optimized to skip sorting for lists with size 0 or 1, + * in order to avoid unnecessary array extraction. + * @param array the array to sort + * @see java.util.Arrays#sort(Object[], java.util.Comparator) + */ + public static void sort(Object[] array) { + if (array.length > 1) { + Arrays.sort(array, INSTANCE); + } + } + } -- GitLab