Multicaster support for events with null source

Update `AbstractApplicationEventMulticaster` to support `EventObjects`
with a null source (which can happen if they have been serialized).

Issue: SPR-10945
This commit is contained in:
Phillip Webb 2013-10-21 11:55:16 -07:00
parent 6d882b149d
commit b0ff834ee3
5 changed files with 45 additions and 31 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2013 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -28,6 +28,7 @@ import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.core.OrderComparator; import org.springframework.core.OrderComparator;
import org.springframework.util.ObjectUtils;
/** /**
* Abstract implementation of the {@link ApplicationEventMulticaster} interface, * Abstract implementation of the {@link ApplicationEventMulticaster} interface,
@ -134,7 +135,8 @@ public abstract class AbstractApplicationEventMulticaster implements Application
*/ */
protected Collection<ApplicationListener> getApplicationListeners(ApplicationEvent event) { protected Collection<ApplicationListener> getApplicationListeners(ApplicationEvent event) {
Class<? extends ApplicationEvent> eventType = event.getClass(); Class<? extends ApplicationEvent> eventType = event.getClass();
Class sourceType = event.getSource().getClass(); Object source = event.getSource();
Class sourceType = (source == null ? null : source.getClass());
ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType); ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
ListenerRetriever retriever = this.retrieverCache.get(cacheKey); ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) { if (retriever != null) {
@ -212,12 +214,14 @@ public abstract class AbstractApplicationEventMulticaster implements Application
return true; return true;
} }
ListenerCacheKey otherKey = (ListenerCacheKey) other; ListenerCacheKey otherKey = (ListenerCacheKey) other;
return (this.eventType.equals(otherKey.eventType) && this.sourceType.equals(otherKey.sourceType)); return ObjectUtils.nullSafeEquals(this.eventType, otherKey.eventType)
&& ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return this.eventType.hashCode() * 29 + this.sourceType.hashCode(); return ObjectUtils.nullSafeHashCode(this.eventType) * 29
+ ObjectUtils.nullSafeHashCode(this.sourceType);
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2013 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -77,7 +77,7 @@ public class SourceFilteringListener implements SmartApplicationListener {
@Override @Override
public boolean supportsSourceType(Class<?> sourceType) { public boolean supportsSourceType(Class<?> sourceType) {
return sourceType.isInstance(this.source); return (sourceType != null && sourceType.isInstance(this.source));
} }
@Override @Override

View File

@ -16,8 +16,13 @@
package org.springframework.context; package org.springframework.context;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Locale; import java.util.Locale;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.AbstractListableBeanFactoryTests; import org.springframework.beans.factory.xml.AbstractListableBeanFactoryTests;
import org.springframework.tests.sample.beans.LifecycleBean; import org.springframework.tests.sample.beans.LifecycleBean;
@ -129,11 +134,29 @@ public abstract class AbstractApplicationContextTests extends AbstractListableBe
} }
public void testEvents() throws Exception { public void testEvents() throws Exception {
doTestEvents(this.listener, this.parentListener, new MyEvent(this));
}
@Test
public void testEventsWithNoSource() throws Exception {
// See SPR-10945 Serialized events result in a null source
MyEvent event = new MyEvent(this);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(event);
oos.close();
event = (MyEvent) new ObjectInputStream(new ByteArrayInputStream(
bos.toByteArray())).readObject();
doTestEvents(this.listener, this.parentListener, event);
}
protected void doTestEvents(TestListener listener, TestListener parentListener,
MyEvent event) {
listener.zeroCounter(); listener.zeroCounter();
parentListener.zeroCounter(); parentListener.zeroCounter();
assertTrue("0 events before publication", listener.getEventCount() == 0); assertTrue("0 events before publication", listener.getEventCount() == 0);
assertTrue("0 parent events before publication", parentListener.getEventCount() == 0); assertTrue("0 parent events before publication", parentListener.getEventCount() == 0);
this.applicationContext.publishEvent(new MyEvent(this)); this.applicationContext.publishEvent(event);
assertTrue("1 events after publication, not " + listener.getEventCount(), listener.getEventCount() == 1); assertTrue("1 events after publication, not " + listener.getEventCount(), listener.getEventCount() == 1);
assertTrue("1 parent events after publication", parentListener.getEventCount() == 1); assertTrue("1 parent events after publication", parentListener.getEventCount() == 1);
} }

View File

@ -45,19 +45,13 @@ public abstract class AbstractXmlWebApplicationContextTests extends AbstractAppl
* @see org.springframework.context.AbstractApplicationContextTests#testEvents() * @see org.springframework.context.AbstractApplicationContextTests#testEvents()
*/ */
@Override @Override
public void testEvents() throws Exception { protected void doTestEvents(TestListener listener, TestListener parentListener,
TestListener listener = (TestListener) this.applicationContext.getBean("testListener"); MyEvent event) {
listener.zeroCounter(); TestListener listenerBean = (TestListener) this.applicationContext.getBean("testListener");
TestListener parentListener = (TestListener) this.applicationContext.getParent().getBean("parentListener"); TestListener parentListenerBean = (TestListener) this.applicationContext.getParent().getBean("parentListener");
parentListener.zeroCounter(); super.doTestEvents(listenerBean, parentListenerBean, event);
parentListener.zeroCounter(); };
assertTrue("0 events before publication", listener.getEventCount() == 0);
assertTrue("0 parent events before publication", parentListener.getEventCount() == 0);
this.applicationContext.publishEvent(new MyEvent(this));
assertTrue("1 events after publication, not " + listener.getEventCount(), listener.getEventCount() == 1);
assertTrue("1 parent events after publication", parentListener.getEventCount() == 1);
}
@Override @Override
public void testCount() { public void testCount() {

View File

@ -96,18 +96,11 @@ public class XmlWebApplicationContextTests extends AbstractApplicationContextTes
* @see org.springframework.context.AbstractApplicationContextTests#testEvents() * @see org.springframework.context.AbstractApplicationContextTests#testEvents()
*/ */
@Override @Override
public void testEvents() throws Exception { protected void doTestEvents(TestListener listener, TestListener parentListener,
TestListener listener = (TestListener) this.applicationContext.getBean("testListener"); MyEvent event) {
listener.zeroCounter(); TestListener listenerBean = (TestListener) this.applicationContext.getBean("testListener");
TestListener parentListener = (TestListener) this.applicationContext.getParent().getBean("parentListener"); TestListener parentListenerBean = (TestListener) this.applicationContext.getParent().getBean("parentListener");
parentListener.zeroCounter(); super.doTestEvents(listenerBean, parentListenerBean, event);
parentListener.zeroCounter();
assertTrue("0 events before publication", listener.getEventCount() == 0);
assertTrue("0 parent events before publication", parentListener.getEventCount() == 0);
this.applicationContext.publishEvent(new MyEvent(this));
assertTrue("1 events after publication, not " + listener.getEventCount(), listener.getEventCount() == 1);
assertTrue("1 parent events after publication", parentListener.getEventCount() == 1);
} }
@Override @Override