diff --git a/org.springframework.context/src/main/java/org/springframework/context/ApplicationListener.java b/org.springframework.context/src/main/java/org/springframework/context/ApplicationListener.java index 5446d75246e..0e57ebd51a9 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/ApplicationListener.java +++ b/org.springframework.context/src/main/java/org/springframework/context/ApplicationListener.java @@ -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 extends EventListener { /** * Handle an application event. * @param event the event to respond to */ - void onApplicationEvent(ApplicationEvent event); + void onApplicationEvent(E event); } 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 0e604329830..5eab8f7b86b 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 @@ -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 applicationListeners = new LinkedHashSet(); + private final Set applicationListeners = new LinkedHashSet(); + private final Set applicationListenerBeans = new LinkedHashSet(); - /** - * 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 newColl = concurrent ? - new CopyOnWriteArraySet() : new LinkedHashSet(); - // 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. - *

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 newColl = - (Collection) 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. - *

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 getApplicationListeners() { - return this.applicationListeners; + 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); + } + for (String listenerBeanName : applicationListenerBeans) { + allListeners.add(this.beanFactory.getBean(listenerBeanName, ApplicationListener.class)); + } + } + Collections.sort(allListeners, new OrderComparator()); + return allListeners; } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/event/ApplicationEventMulticaster.java b/org.springframework.context/src/main/java/org/springframework/context/event/ApplicationEventMulticaster.java index 3998bb33a0b..871400aa794 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/event/ApplicationEventMulticaster.java +++ b/org.springframework.context/src/main/java/org/springframework/context/event/ApplicationEventMulticaster.java @@ -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. 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 new file mode 100644 index 00000000000..c3a27a872e7 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java @@ -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 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 getGenericEventType(Class 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; + } + +} 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 31c2ec224b0..9e89a18f6c1 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 @@ -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. *

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); + } + }); + } } } 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 new file mode 100644 index 00000000000..e5932eef436 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/event/SmartApplicationListener.java @@ -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 eventType); + +} 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 0a4287bd4e4..8a8b0bd0ca2 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 @@ -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 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. *

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( diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 67b2e4bd3f2..d54d7ff85b8 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.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. @@ -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 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 Map getBeansOfType(Class type) throws BeansException { return getBeanFactory().getBeansOfType(type); } - public Map getBeansOfType(Class type, boolean includePrototypes, boolean allowEagerInit) + public Map getBeansOfType(Class type, boolean includeNonSingletons, boolean allowEagerInit) throws BeansException { - return getBeanFactory().getBeansOfType(type, includePrototypes, allowEagerInit); + return getBeanFactory().getBeansOfType(type, includeNonSingletons, allowEagerInit); } public Map getBeansWithAnnotation(Class annotationType) 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 0d5dcdb0b5b..38a02101e81 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 @@ -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. @@ -16,19 +16,16 @@ package org.springframework.context.event; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import org.aopalliance.intercept.MethodInvocation; import org.easymock.EasyMock; import static org.easymock.EasyMock.*; -import static org.junit.Assert.*; -import org.junit.Test; import org.junit.Assert; +import static org.junit.Assert.assertTrue; +import org.junit.Test; -import org.springframework.beans.FatalBeanException; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.BeanCurrentlyInCreationException; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; @@ -37,6 +34,7 @@ import org.springframework.context.ApplicationListener; import org.springframework.context.BeanThatBroadcasts; import org.springframework.context.BeanThatListens; import org.springframework.context.support.StaticApplicationContext; +import org.springframework.core.Ordered; /** * Unit and integration tests for the ApplicationContext event support. @@ -46,70 +44,9 @@ import org.springframework.context.support.StaticApplicationContext; */ public class ApplicationContextEventTests { - private AbstractApplicationEventMulticaster getMulticaster() { - return new AbstractApplicationEventMulticaster() { - public void multicastEvent(ApplicationEvent event) { - } - }; - } - - @Test - public void multicasterNewCollectionClass() { - AbstractApplicationEventMulticaster mc = getMulticaster(); - - mc.addApplicationListener(new NoOpApplicationListener()); - - mc.setCollectionClass(ArrayList.class); - - assertEquals(1, mc.getApplicationListeners().size()); - assertEquals(ArrayList.class, mc.getApplicationListeners().getClass()); - } - - @Test(expected = IllegalArgumentException.class) - public void multicasterInvalidCollectionClass_NotEvenACollectionType() { - AbstractApplicationEventMulticaster mc = getMulticaster(); - mc.setCollectionClass(ApplicationContextEventTests.class); - } - - @Test(expected = FatalBeanException.class) - public void multicasterInvalidCollectionClass_PassingAnInterfaceNotAConcreteClass() { - AbstractApplicationEventMulticaster mc = getMulticaster(); - mc.setCollectionClass(List.class); - } - - @Test(expected = IllegalArgumentException.class) - public void multicasterNullCollectionClass() { - AbstractApplicationEventMulticaster mc = getMulticaster(); - mc.setCollectionClass(null); - } - - @Test - public void multicasterRemoveAll() { - AbstractApplicationEventMulticaster mc = getMulticaster(); - mc.addApplicationListener(new NoOpApplicationListener()); - mc.removeAllListeners(); - - assertEquals(0, mc.getApplicationListeners().size()); - } - - @Test - public void multicasterRemoveOne() { - AbstractApplicationEventMulticaster mc = getMulticaster(); - ApplicationListener one = new NoOpApplicationListener(); - ApplicationListener two = new NoOpApplicationListener(); - mc.addApplicationListener(one); - mc.addApplicationListener(two); - - mc.removeApplicationListener(one); - - assertEquals(1, mc.getApplicationListeners().size()); - assertTrue("Remaining listener present", mc.getApplicationListeners().contains(two)); - } - @Test public void simpleApplicationEventMulticaster() { ApplicationListener listener = EasyMock.createMock(ApplicationListener.class); - ApplicationEvent evt = new ContextClosedEvent(new StaticApplicationContext()); listener.onApplicationEvent(evt); @@ -117,14 +54,25 @@ public class ApplicationContextEventTests { smc.addApplicationListener(listener); replay(listener); - smc.multicastEvent(evt); - verify(listener); } @Test - public void testEvenPublicationInterceptor() throws Throwable { + public void orderedListeners() { + MyOrderedListener1 listener1 = new MyOrderedListener1(); + MyOrderedListener2 listener2 = new MyOrderedListener2(listener1); + + SimpleApplicationEventMulticaster smc = new SimpleApplicationEventMulticaster(); + smc.addApplicationListener(listener2); + smc.addApplicationListener(listener1); + + smc.multicastEvent(new MyEvent(this)); + smc.multicastEvent(new MyOtherEvent(this)); + } + + @Test + public void testEventPublicationInterceptor() throws Throwable { MethodInvocation invocation = EasyMock.createMock(MethodInvocation.class); ApplicationContext ctx = EasyMock.createMock(ApplicationContext.class); @@ -134,37 +82,15 @@ public class ApplicationContextEventTests { interceptor.afterPropertiesSet(); expect(invocation.proceed()).andReturn(new Object()); - expect(invocation.getThis()).andReturn(new Object()); - ctx.publishEvent(isA(MyEvent.class)); - replay(invocation, ctx); - interceptor.invoke(invocation); - verify(invocation, ctx); } @Test - public void listenerAndBroadcasterWithUnresolvableCircularReference() { - StaticApplicationContext context = new StaticApplicationContext(); - context.setDisplayName("listener context"); - context.registerBeanDefinition("broadcaster", new RootBeanDefinition(BeanThatBroadcasts.class)); - RootBeanDefinition listenerDef = new RootBeanDefinition(BeanThatListens.class); - listenerDef.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference("broadcaster")); - context.registerBeanDefinition("listener", listenerDef); - try { - context.refresh(); - fail("Should have thrown BeanCreationException with nested BeanCurrentlyInCreationException"); - } - catch (BeanCreationException ex) { - assertTrue(ex.contains(BeanCurrentlyInCreationException.class)); - } - } - - @Test - public void listenerAndBroadcasterWithResolvableCircularReference() { + public void listenerAndBroadcasterWithCircularReference() { StaticApplicationContext context = new StaticApplicationContext(); context.registerBeanDefinition("broadcaster", new RootBeanDefinition(BeanThatBroadcasts.class)); RootBeanDefinition listenerDef = new RootBeanDefinition(BeanThatListens.class); @@ -177,6 +103,7 @@ public class ApplicationContextEventTests { Assert.assertEquals("The event was not received by the listener", 2, broadcaster.receivedCount); } + public static class MyEvent extends ApplicationEvent { public MyEvent(Object source) { @@ -184,11 +111,52 @@ public class ApplicationContextEventTests { } } - private static final class NoOpApplicationListener implements ApplicationListener { + + public static class MyOtherEvent extends ApplicationEvent { + + public MyOtherEvent(Object source) { + super(source); + } + } + + + public static class MyOrderedListener1 implements ApplicationListener, Ordered { + + public final Set seenEvents = new HashSet(); public void onApplicationEvent(ApplicationEvent event) { + this.seenEvents.add(event); } + public int getOrder() { + return 0; + } + } + + + public interface MyOrderedListenerIfc extends ApplicationListener, Ordered { + } + + + public static abstract class MyOrderedListenerBase implements MyOrderedListenerIfc { + + public int getOrder() { + return 1; + } + } + + + public static class MyOrderedListener2 extends MyOrderedListenerBase { + + private final MyOrderedListener1 otherListener; + + public MyOrderedListener2(MyOrderedListener1 otherListener) { + this.otherListener = otherListener; + } + + public void onApplicationEvent(MyEvent event) { + assertTrue(otherListener.seenEvents.contains(event)); + } } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java b/org.springframework.core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java index 0df5d9e75bb..402b9961129 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java +++ b/org.springframework.core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.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. @@ -45,7 +45,7 @@ public abstract class GenericCollectionTypeResolver { * @param collectionClass the collection class to introspect * @return the generic type, or null if none */ - public static Class getCollectionType(Class collectionClass) { + public static Class getCollectionType(Class collectionClass) { return extractTypeFromClass(collectionClass, Collection.class, 0); } @@ -55,7 +55,7 @@ public abstract class GenericCollectionTypeResolver { * @param mapClass the map class to introspect * @return the generic type, or null if none */ - public static Class getMapKeyType(Class mapClass) { + public static Class getMapKeyType(Class mapClass) { return extractTypeFromClass(mapClass, Map.class, 0); } @@ -65,7 +65,7 @@ public abstract class GenericCollectionTypeResolver { * @param mapClass the map class to introspect * @return the generic type, or null if none */ - public static Class getMapValueType(Class mapClass) { + public static Class getMapValueType(Class mapClass) { return extractTypeFromClass(mapClass, Map.class, 1); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/GenericTypeResolver.java b/org.springframework.core/src/main/java/org/springframework/core/GenericTypeResolver.java index 4e3dfc534cb..ae9c3f825bf 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/org.springframework.core/src/main/java/org/springframework/core/GenericTypeResolver.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. @@ -99,6 +99,18 @@ public abstract class GenericTypeResolver { return (rawType instanceof Class ? (Class) rawType : method.getReturnType()); } + /** + * Resolve the given type variable against the given class. + * @param tv the type variable to resolve + * @param clazz the class that defines the type variable + * somewhere in its inheritance hierarchy + * @return the resolved type that the variable can get replaced with, + * or null if none found + */ + public static Type resolveTypeVariable(TypeVariable tv, Class clazz) { + return getTypeVariableMap(clazz).get(tv); + } + /** * Resolve the specified generic type against the given TypeVariable map. diff --git a/org.springframework.core/src/main/java/org/springframework/core/Ordered.java b/org.springframework.core/src/main/java/org/springframework/core/Ordered.java index 5a711f334e5..86e8dca364f 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/Ordered.java +++ b/org.springframework.core/src/main/java/org/springframework/core/Ordered.java @@ -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. @@ -51,18 +51,13 @@ public interface Ordered { /** * Return the order value of this object, with a * higher value meaning greater in terms of sorting. - *

Normally starting with 0 or 1, with {@link #LOWEST_PRECEDENCE} - * indicating greatest. Same order values will result in arbitrary - * positions for the affected objects. - *

Higher value can be interpreted as lower priority, - * consequently the first object has highest priority + *

Normally starting with 0, with Integer.MAX_VALUE + * indicating the greatest value. Same order values will result + * in arbitrary positions for the affected objects. + *

Higher values can be interpreted as lower priority. As a + * consequence, the object with the lowest value has highest priority * (somewhat analogous to Servlet "load-on-startup" values). - *

Note that order values below 0 are reserved for framework - * purposes. Application-specified values should always be 0 or - * greater, with only framework components (internal or third-party) - * supposed to use lower values. * @return the order value - * @see #LOWEST_PRECEDENCE */ int getOrder(); diff --git a/org.springframework.core/src/test/java/org/springframework/core/GenericCollectionTypeResolverTests.java b/org.springframework.core/src/test/java/org/springframework/core/GenericCollectionTypeResolverTests.java index 7bf042545b8..9840fb60b5b 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/GenericCollectionTypeResolverTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/GenericCollectionTypeResolverTests.java @@ -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. @@ -19,6 +19,7 @@ package org.springframework.core; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.AbstractMap; +import java.util.AbstractSet; import java.util.Date; import java.util.List; import java.util.Map; @@ -101,12 +102,22 @@ public class GenericCollectionTypeResolverTests extends AbstractGenericsTests { GenericCollectionTypeResolver.getCollectionReturnType(getter)); } - - private abstract class CustomMap extends AbstractMap { + public void testClassResolution() { + assertEquals(String.class, GenericCollectionTypeResolver.getCollectionType(CustomSet.class)); + assertEquals(String.class, GenericCollectionTypeResolver.getMapKeyType(CustomMap.class)); + assertEquals(Integer.class, GenericCollectionTypeResolver.getMapValueType(CustomMap.class)); } - private abstract class OtherCustomMap implements Map { + private abstract class CustomSet extends AbstractSet { + } + + + private abstract class CustomMap extends AbstractMap { + } + + + private abstract class OtherCustomMap implements Map { } diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/FrameworkPortlet.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/FrameworkPortlet.java index e1822197791..3797f586210 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/FrameworkPortlet.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/FrameworkPortlet.java @@ -35,7 +35,6 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextException; -import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ContextRefreshedEvent; @@ -102,7 +101,8 @@ import org.springframework.web.portlet.context.XmlPortletApplicationContext; * @see #setContextConfigLocation * @see #setNamespace */ -public abstract class FrameworkPortlet extends GenericPortletBean implements ApplicationListener { +public abstract class FrameworkPortlet extends GenericPortletBean + implements ApplicationListener { /** * Default context class for FrameworkPortlet. @@ -417,11 +417,9 @@ public abstract class FrameworkPortlet extends GenericPortletBean implements App * triggering a refresh of this servlet's context-dependent state. * @param event the incoming ApplicationContext event */ - public void onApplicationEvent(ApplicationEvent event) { - if (event instanceof ContextRefreshedEvent) { - this.refreshEventReceived = true; - onRefresh(((ContextRefreshedEvent) event).getApplicationContext()); - } + public void onApplicationEvent(ContextRefreshedEvent event) { + this.refreshEventReceived = true; + onRefresh(event.getApplicationContext()); } /** diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java index c71b5dcbef6..a2a4d95db65 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java @@ -26,7 +26,6 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextException; -import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ContextRefreshedEvent; @@ -94,7 +93,8 @@ import org.springframework.web.util.WebUtils; * @see #setContextConfigLocation * @see #setNamespace */ -public abstract class FrameworkServlet extends HttpServletBean implements ApplicationListener { +public abstract class FrameworkServlet extends HttpServletBean + implements ApplicationListener { /** * Suffix for WebApplicationContext namespaces. If a servlet of this class is @@ -493,11 +493,9 @@ public abstract class FrameworkServlet extends HttpServletBean implements Applic * triggering a refresh of this servlet's context-dependent state. * @param event the incoming ApplicationContext event */ - public void onApplicationEvent(ApplicationEvent event) { - if (event instanceof ContextRefreshedEvent) { - this.refreshEventReceived = true; - onRefresh(((ContextRefreshedEvent) event).getApplicationContext()); - } + public void onApplicationEvent(ContextRefreshedEvent event) { + this.refreshEventReceived = true; + onRefresh(event.getApplicationContext()); } /**