Skip to content

Commit 0e1b04d

Browse files
committed
inner beans detected as ApplicationListeners as well (SPR-6049)
1 parent 94aad0b commit 0e1b04d

File tree

4 files changed

+72
-12
lines changed

4 files changed

+72
-12
lines changed

org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life
9999
* Add a new ApplicationListener that will be notified on context events
100100
* such as context refresh and context shutdown.
101101
* <p>Note that any ApplicationListener registered here will be applied
102-
* on refresh of this context. If a listener is added after the initial
103-
* refresh, it will be applied on next refresh of the context.
102+
* on refresh if the context is not active yet, or on the fly with the
103+
* current event multicaster in case of a context that is already active.
104104
* @param listener the ApplicationListener to register
105105
* @see org.springframework.context.event.ContextRefreshedEvent
106106
* @see org.springframework.context.event.ContextClosedEvent

org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@
2020
import java.lang.annotation.Annotation;
2121
import java.security.AccessControlException;
2222
import java.util.ArrayList;
23+
import java.util.Collection;
2324
import java.util.Date;
2425
import java.util.LinkedHashMap;
26+
import java.util.LinkedHashSet;
2527
import java.util.List;
2628
import java.util.Locale;
2729
import java.util.Map;
30+
import java.util.Set;
2831

2932
import org.apache.commons.logging.Log;
3033
import org.apache.commons.logging.LogFactory;
@@ -193,7 +196,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
193196
private ApplicationEventMulticaster applicationEventMulticaster;
194197

195198
/** Statically specified listeners */
196-
private List<ApplicationListener> applicationListeners = new ArrayList<ApplicationListener>();
199+
private Set<ApplicationListener> applicationListeners = new LinkedHashSet<ApplicationListener>();
197200

198201

199202
/**
@@ -362,13 +365,18 @@ public List<BeanFactoryPostProcessor> getBeanFactoryPostProcessors() {
362365
}
363366

364367
public void addApplicationListener(ApplicationListener listener) {
365-
this.applicationListeners.add(listener);
368+
if (isActive()) {
369+
addListener(listener);
370+
}
371+
else {
372+
this.applicationListeners.add(listener);
373+
}
366374
}
367375

368376
/**
369377
* Return the list of statically specified ApplicationListeners.
370378
*/
371-
public List<ApplicationListener> getApplicationListeners() {
379+
public Collection<ApplicationListener> getApplicationListeners() {
372380
return this.applicationListeners;
373381
}
374382

@@ -817,7 +825,7 @@ protected void registerListeners() {
817825
// Do not initialize FactoryBeans here: We need to leave all regular beans
818826
// uninitialized to let post-processors apply to them!
819827
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
820-
for (final String lisName : listenerBeanNames) {
828+
for (String lisName : listenerBeanNames) {
821829
getApplicationEventMulticaster().addApplicationListenerBean(lisName);
822830
}
823831
}

org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,18 @@
1919
import java.security.AccessControlContext;
2020
import java.security.AccessController;
2121
import java.security.PrivilegedAction;
22+
import java.util.Map;
23+
import java.util.concurrent.ConcurrentHashMap;
24+
25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
2227

2328
import org.springframework.beans.BeansException;
24-
import org.springframework.beans.factory.config.BeanPostProcessor;
29+
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
30+
import org.springframework.beans.factory.support.RootBeanDefinition;
2531
import org.springframework.context.ApplicationContextAware;
2632
import org.springframework.context.ApplicationEventPublisherAware;
33+
import org.springframework.context.ApplicationListener;
2734
import org.springframework.context.ConfigurableApplicationContext;
2835
import org.springframework.context.MessageSourceAware;
2936
import org.springframework.context.ResourceLoaderAware;
@@ -48,10 +55,14 @@
4855
* @see org.springframework.context.ApplicationContextAware
4956
* @see org.springframework.context.support.AbstractApplicationContext#refresh()
5057
*/
51-
class ApplicationContextAwareProcessor implements BeanPostProcessor {
58+
class ApplicationContextAwareProcessor implements MergedBeanDefinitionPostProcessor {
59+
60+
private final Log logger = LogFactory.getLog(getClass());
5261

5362
private final ConfigurableApplicationContext applicationContext;
5463

64+
private final Map<String, Boolean> singletonNames = new ConcurrentHashMap<String, Boolean>();
65+
5566

5667
/**
5768
* Create a new ApplicationContextAwareProcessor for the given context.
@@ -61,7 +72,13 @@ public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicati
6172
}
6273

6374

64-
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
75+
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) {
76+
if (!this.applicationContext.containsBean(beanName) && beanDefinition.isSingleton()) {
77+
this.singletonNames.put(beanName, Boolean.TRUE);
78+
}
79+
}
80+
81+
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
6582
AccessControlContext acc = null;
6683

6784
if (System.getSecurityManager() != null &&
@@ -73,19 +90,19 @@ public Object postProcessBeforeInitialization(final Object bean, String beanName
7390
if (acc != null) {
7491
AccessController.doPrivileged(new PrivilegedAction<Object>() {
7592
public Object run() {
76-
doProcess(bean);
93+
doProcess(bean, beanName);
7794
return null;
7895
}
7996
}, acc);
8097
}
8198
else {
82-
doProcess(bean);
99+
doProcess(bean, beanName);
83100
}
84101

85102
return bean;
86103
}
87104

88-
private void doProcess(Object bean) {
105+
private void doProcess(Object bean, String beanName) {
89106
if (bean instanceof ResourceLoaderAware) {
90107
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
91108
}
@@ -98,6 +115,27 @@ private void doProcess(Object bean) {
98115
if (bean instanceof ApplicationContextAware) {
99116
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
100117
}
118+
119+
if (bean instanceof ApplicationListener) {
120+
if (!this.applicationContext.containsBean(beanName)) {
121+
// not a top-level bean - not detected as a listener by getBeanNamesForType retrieval
122+
Boolean flag = this.singletonNames.get(beanName);
123+
if (Boolean.TRUE.equals(flag)) {
124+
// inner singleton bean: register on the fly
125+
this.applicationContext.addApplicationListener((ApplicationListener) bean);
126+
}
127+
else if (flag == null) {
128+
// inner bean with other scope - can't reliably process events
129+
if (logger.isWarnEnabled()) {
130+
logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +
131+
"but is not reachable for event multicasting by its containing ApplicationContext " +
132+
"because it does not have singleton scope. Only top-level listener beans are allowed " +
133+
"to be of non-singleton scope.");
134+
}
135+
this.singletonNames.put(beanName, Boolean.FALSE);
136+
}
137+
}
138+
}
101139
}
102140

103141
public Object postProcessAfterInitialization(Object bean, String name) {

org.springframework.context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import static org.junit.Assert.*;
2626
import org.junit.Test;
2727

28+
import org.springframework.beans.TestBean;
2829
import org.springframework.beans.factory.config.RuntimeBeanReference;
2930
import org.springframework.beans.factory.support.RootBeanDefinition;
3031
import org.springframework.context.ApplicationContext;
@@ -126,6 +127,19 @@ public void listenerAndBroadcasterWithCircularReference() {
126127
assertEquals("The event was not received by the listener", 2, broadcaster.receivedCount);
127128
}
128129

130+
@Test
131+
public void innerBeanAsListener() {
132+
StaticApplicationContext context = new StaticApplicationContext();
133+
RootBeanDefinition listenerDef = new RootBeanDefinition(TestBean.class);
134+
listenerDef.getPropertyValues().addPropertyValue("friends", new RootBeanDefinition(BeanThatListens.class));
135+
context.registerBeanDefinition("listener", listenerDef);
136+
context.refresh();
137+
context.publishEvent(new MyEvent(this));
138+
context.publishEvent(new MyEvent(this));
139+
TestBean listener = context.getBean(TestBean.class);
140+
assertEquals(3, ((BeanThatListens) listener.getFriends().iterator().next()).getEventCount());
141+
}
142+
129143

130144
public static class MyEvent extends ApplicationEvent {
131145

0 commit comments

Comments
 (0)