ApplicationListener beans get obtained on demand, supporting non-singletons as well; ApplicationListeners will be called in the order according to the Ordered contract; generified ApplicationListener interface

This commit is contained in:
Juergen Hoeller
2009-02-09 18:26:30 +00:00
parent 6609386e41
commit 60392d6e74
15 changed files with 338 additions and 220 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2006 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.
@@ -24,14 +24,15 @@ import java.util.EventListener;
* for the Observer design pattern.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see org.springframework.context.event.ApplicationEventMulticaster
*/
public interface ApplicationListener extends EventListener {
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(ApplicationEvent event);
void onApplicationEvent(E event);
}

View File

@@ -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.
@@ -17,11 +17,15 @@
package org.springframework.context.event;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.LinkedList;
import java.util.Set;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.ApplicationListener;
import org.springframework.core.OrderComparator;
/**
* Abstract implementation of the {@link ApplicationEventMulticaster} interface,
@@ -43,73 +47,47 @@ import org.springframework.context.ApplicationListener;
* @see #getApplicationListeners()
* @see SimpleApplicationEventMulticaster
*/
public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster {
public abstract class AbstractApplicationEventMulticaster
implements ApplicationEventMulticaster, BeanFactoryAware {
/** Collection of ApplicationListeners */
private Collection<ApplicationListener> applicationListeners = new LinkedHashSet<ApplicationListener>();
private final Set<ApplicationListener> applicationListeners = new LinkedHashSet<ApplicationListener>();
private final Set<String> applicationListenerBeans = new LinkedHashSet<String>();
/**
* Set whether this multicaster should expect concurrent updates at runtime
* (i.e. after context startup finished). In case of concurrent updates,
* a copy-on-write strategy is applied, keeping iteration (for multicasting)
* without synchronization while still making listener updates thread-safe.
*/
public void setConcurrentUpdates(boolean concurrent) {
Collection<ApplicationListener> newColl = concurrent ?
new CopyOnWriteArraySet<ApplicationListener>() : new LinkedHashSet<ApplicationListener>();
// Add all previously registered listeners (usually none).
newColl.addAll(this.applicationListeners);
this.applicationListeners = newColl;
}
/**
* Specify the collection class to use. Can be populated with a fully
* qualified class name when defined in a Spring application context.
* <p>Default is a linked HashSet, keeping the registration order.
* Note that a Set class specified will not permit multiple instances
* of the same listener, while a List class will allow for registering
* the same listener multiple times.
*/
@SuppressWarnings("unchecked")
public void setCollectionClass(Class collectionClass) {
if (collectionClass == null) {
throw new IllegalArgumentException("'collectionClass' must not be null");
}
if (!Collection.class.isAssignableFrom(collectionClass)) {
throw new IllegalArgumentException("'collectionClass' must implement [java.util.Collection]");
}
// Create desired collection instance.
Collection<ApplicationListener> newColl =
(Collection<ApplicationListener>) BeanUtils.instantiateClass(collectionClass);
// Add all previously registered listeners (usually none).
newColl.addAll(this.applicationListeners);
this.applicationListeners = newColl;
}
private BeanFactory beanFactory;
public void addApplicationListener(ApplicationListener listener) {
this.applicationListeners.add(listener);
}
public void removeApplicationListener(ApplicationListener listener) {
this.applicationListeners.remove(listener);
public void addApplicationListenerBean(String listenerBeanName) {
this.applicationListenerBeans.add(listenerBeanName);
}
public void removeAllListeners() {
this.applicationListeners.clear();
public final void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
/**
* Return the current Collection of ApplicationListeners.
* <p>Note that this is the raw Collection of ApplicationListeners,
* potentially modified when new listeners get registered or
* existing ones get removed. This Collection is not a snapshot copy.
* @return a Collection of ApplicationListeners
* @see org.springframework.context.ApplicationListener
*/
protected Collection<ApplicationListener> getApplicationListeners() {
return this.applicationListeners;
LinkedList<ApplicationListener> allListeners =
new LinkedList<ApplicationListener>(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);
}
for (String listenerBeanName : applicationListenerBeans) {
allListeners.add(this.beanFactory.getBean(listenerBeanName, ApplicationListener.class));
}
}
Collections.sort(allListeners, new OrderComparator());
return allListeners;
}
}

View File

@@ -1,12 +1,12 @@
/*
* Copyright 2002-2005 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.
* 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.
@@ -27,6 +27,7 @@ import org.springframework.context.ApplicationListener;
* as a helper to publish events to listeners.
*
* @author Rod Johnson
* @author Juergen Hoeller
*/
public interface ApplicationEventMulticaster {
@@ -37,17 +38,10 @@ public interface ApplicationEventMulticaster {
void addApplicationListener(ApplicationListener listener);
/**
* Remove a listener from the notification list.
* @param listener the listener to remove
* Add a listener to be notified of all events.
* @param listenerBeanName the name of the listener bean to add
*/
void removeApplicationListener(ApplicationListener listener);
/**
* Remove all listeners registered with this multicaster.
* It will perform no action on event notification until more
* listeners are registered.
*/
void removeAllListeners();
void addApplicationListenerBean(String listenerBeanName);
/**
* Multicast the given application event to appropriate listeners.

View File

@@ -0,0 +1,96 @@
/*
* 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.
* 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.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
/**
* {@link SmartApplicationListener} adapter that determines supported event types
* through introspecting the generically declared type of the target listener.
*
* @author Juergen Hoeller
* @since 3.0
* @see org.springframework.context.ApplicationListener#onApplicationEvent
*/
public class GenericApplicationListenerAdapter implements SmartApplicationListener {
private final ApplicationListener delegate;
/**
* Create a new GenericApplicationListener for the given delegate.
* @param delegate the delegate listener to be invoked
*/
public GenericApplicationListenerAdapter(ApplicationListener delegate) {
Assert.notNull(delegate, "Delegate listener must not be null");
this.delegate = delegate;
}
@SuppressWarnings("unchecked")
public void onApplicationEvent(ApplicationEvent event) {
this.delegate.onApplicationEvent(event);
}
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return getGenericEventType(this.delegate.getClass()).isAssignableFrom(eventType);
}
public int getOrder() {
return (this.delegate instanceof Ordered ? ((Ordered) this.delegate).getOrder() : Ordered.LOWEST_PRECEDENCE);
}
@SuppressWarnings("unchecked")
private Class<? extends ApplicationEvent> getGenericEventType(Class<? extends ApplicationListener> currentClass) {
Class classToIntrospect = currentClass;
while (classToIntrospect != null) {
Type[] ifcs = classToIntrospect.getGenericInterfaces();
for (Type ifc : ifcs) {
if (ifc instanceof ParameterizedType) {
ParameterizedType paramIfc = (ParameterizedType) ifc;
Type rawType = paramIfc.getRawType();
if (ApplicationListener.class.equals(rawType)) {
Type arg = paramIfc.getActualTypeArguments()[0];
if (arg instanceof TypeVariable) {
arg = GenericTypeResolver.resolveTypeVariable((TypeVariable) arg, this.delegate.getClass());
}
if (arg instanceof Class) {
return (Class) arg;
}
}
else if (ApplicationListener.class.isAssignableFrom((Class) rawType)) {
return getGenericEventType((Class) rawType);
}
}
else if (ApplicationListener.class.isAssignableFrom((Class) ifc)) {
return getGenericEventType((Class) ifc);
}
}
classToIntrospect = classToIntrospect.getSuperclass();
}
return ApplicationEvent.class;
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.context.event;
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;
@@ -38,13 +39,26 @@ import org.springframework.core.task.SyncTaskExecutor;
* @author Rod Johnson
* @author Juergen Hoeller
* @see #setTaskExecutor
* @see #setConcurrentUpdates
*/
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
private Executor taskExecutor = new SyncTaskExecutor();
/**
* Create a new SimpleApplicationEventMulticaster.
*/
public SimpleApplicationEventMulticaster() {
}
/**
* Create a new SimpleApplicationEventMulticaster for the given BeanFactory.
*/
public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
setBeanFactory(beanFactory);
}
/**
* Set the TaskExecutor to execute application listeners with.
* <p>Default is a SyncTaskExecutor, executing the listeners synchronously
@@ -71,11 +85,16 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
public void multicastEvent(final ApplicationEvent event) {
for (final ApplicationListener listener : getApplicationListeners()) {
getTaskExecutor().execute(new Runnable() {
public void run() {
listener.onApplicationEvent(event);
}
});
SmartApplicationListener smartListener = (listener instanceof SmartApplicationListener ?
(SmartApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
if (smartListener.supportsEventType(event.getClass())) {
getTaskExecutor().execute(new Runnable() {
@SuppressWarnings("unchecked")
public void run() {
listener.onApplicationEvent(event);
}
});
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.
* 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;
/**
* Extended variant of the standard {@link ApplicationListener} interface,
* exposing further metadata such as the supported event type.
*
* @author Juergen Hoeller
* @since 3.0
*/
public interface SmartApplicationListener extends ApplicationListener, Ordered {
/**
* Determine whether this listener actually supports the given event type.
*/
boolean supportsEventType(Class<? extends ApplicationEvent> eventType);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 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.
@@ -18,6 +18,7 @@ package org.springframework.context.event;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
/**
* {@link org.springframework.context.ApplicationListener} decorator that filters
@@ -30,11 +31,11 @@ import org.springframework.context.ApplicationListener;
* @author Juergen Hoeller
* @since 2.0.5
*/
public class SourceFilteringListener implements ApplicationListener {
public class SourceFilteringListener implements SmartApplicationListener {
private final Object source;
private ApplicationListener delegate;
private SmartApplicationListener delegate;
/**
@@ -46,7 +47,8 @@ public class SourceFilteringListener implements ApplicationListener {
*/
public SourceFilteringListener(Object source, ApplicationListener delegate) {
this.source = source;
this.delegate = delegate;
this.delegate = (delegate instanceof SmartApplicationListener ?
(SmartApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate));
}
/**
@@ -67,12 +69,22 @@ public class SourceFilteringListener implements ApplicationListener {
}
}
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return (this.delegate == null || this.delegate.supportsEventType(eventType));
}
public int getOrder() {
return (this.delegate != null ? this.delegate.getOrder() : Ordered.LOWEST_PRECEDENCE);
}
/**
* Actually process the event, after having filtered according to the
* desired event source already.
* <p>The default implementation invokes the specified delegate, if any.
* @param event the event to process (matching the specified source)
*/
@SuppressWarnings("unchecked")
protected void onApplicationEventInternal(ApplicationEvent event) {
if (this.delegate == null) {
throw new IllegalStateException(

View File

@@ -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.
@@ -19,7 +19,6 @@ package org.springframework.context.support;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
@@ -657,7 +656,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
}
else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster();
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
@@ -685,13 +684,13 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
protected void registerListeners() {
// Register statically specified listeners first.
for (ApplicationListener listener : getApplicationListeners()) {
addListener(listener);
getApplicationEventMulticaster().addApplicationListener(listener);
}
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let post-processors apply to them!
Collection<ApplicationListener> lisBeans = getBeansOfType(ApplicationListener.class, true, false).values();
for (ApplicationListener lisBean : lisBeans) {
addListener(lisBean);
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (final String lisName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(lisName);
}
}
@@ -924,18 +923,18 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
return getBeanFactory().getBeanNamesForType(type);
}
public String[] getBeanNamesForType(Class type, boolean includePrototypes, boolean allowEagerInit) {
return getBeanFactory().getBeanNamesForType(type, includePrototypes, allowEagerInit);
public String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit) {
return getBeanFactory().getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
}
public <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException {
return getBeanFactory().getBeansOfType(type);
}
public <T> Map<String, T> getBeansOfType(Class<T> type, boolean includePrototypes, boolean allowEagerInit)
public <T> Map<String, T> getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
throws BeansException {
return getBeanFactory().getBeansOfType(type, includePrototypes, allowEagerInit);
return getBeanFactory().getBeansOfType(type, includeNonSingletons, allowEagerInit);
}
public Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType)