Skip to content

Commit 2b45988

Browse files
committed
ApplicationListener detection for inner beans behind post-processors
Issue: SPR-14783 (cherry picked from commit c946924)
1 parent e18e7ec commit 2b45988

File tree

4 files changed

+196
-81
lines changed

4 files changed

+196
-81
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.springframework.context.ApplicationEventPublisherAware;
4848
import org.springframework.context.ApplicationListener;
4949
import org.springframework.context.ConfigurableApplicationContext;
50+
import org.springframework.context.EmbeddedValueResolverAware;
5051
import org.springframework.context.EnvironmentAware;
5152
import org.springframework.context.HierarchicalMessageSource;
5253
import org.springframework.context.LifecycleProcessor;
@@ -630,11 +631,12 @@ protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
630631

631632
// Configure the bean factory with context callbacks.
632633
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
634+
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
635+
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
633636
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
634637
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
635638
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
636639
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
637-
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
638640

639641
// BeanFactory interface not registered as resolvable type in a plain factory.
640642
// MessageSource registered (and found for autowiring) as a bean.
@@ -643,6 +645,9 @@ protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
643645
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
644646
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
645647

648+
// Register early post-processor for detecting inner beans as ApplicationListeners.
649+
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
650+
646651
// Detect a LoadTimeWeaver and prepare for weaving, if found.
647652
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
648653
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.support;
18+
19+
import java.util.Map;
20+
import java.util.concurrent.ConcurrentHashMap;
21+
22+
import org.apache.commons.logging.Log;
23+
import org.apache.commons.logging.LogFactory;
24+
25+
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
26+
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
27+
import org.springframework.beans.factory.support.RootBeanDefinition;
28+
import org.springframework.context.ApplicationListener;
29+
import org.springframework.context.event.ApplicationEventMulticaster;
30+
31+
/**
32+
* {@code BeanPostProcessor} that detects beans which implement the {@code ApplicationListener}
33+
* interface. This catches beans that can't reliably be detected by {@code getBeanNamesForType}
34+
* and related operations which only work against top-level beans.
35+
*
36+
* <p>With standard Java serialization, this post-processor won't get serialized as part of
37+
* {@code DisposableBeanAdapter} to begin with. However, with alternative serialization
38+
* mechanisms, {@code DisposableBeanAdapter.writeReplace} might not get used at all, so we
39+
* defensively mark this post-processor's field state as {@code transient}.
40+
*
41+
* @author Juergen Hoeller
42+
* @since 4.3.4
43+
*/
44+
class ApplicationListenerDetector implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor {
45+
46+
private static final Log logger = LogFactory.getLog(ApplicationListenerDetector.class);
47+
48+
private transient final AbstractApplicationContext applicationContext;
49+
50+
private transient final Map<String, Boolean> singletonNames = new ConcurrentHashMap<String, Boolean>(256);
51+
52+
53+
public ApplicationListenerDetector(AbstractApplicationContext applicationContext) {
54+
this.applicationContext = applicationContext;
55+
}
56+
57+
58+
@Override
59+
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
60+
if (this.applicationContext != null && beanDefinition.isSingleton()) {
61+
this.singletonNames.put(beanName, Boolean.TRUE);
62+
}
63+
}
64+
65+
@Override
66+
public Object postProcessBeforeInitialization(Object bean, String beanName) {
67+
return bean;
68+
}
69+
70+
@Override
71+
public Object postProcessAfterInitialization(Object bean, String beanName) {
72+
if (this.applicationContext != null && bean instanceof ApplicationListener) {
73+
// potentially not detected as a listener by getBeanNamesForType retrieval
74+
Boolean flag = this.singletonNames.get(beanName);
75+
if (Boolean.TRUE.equals(flag)) {
76+
// singleton bean (top-level or inner): register on the fly
77+
this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
78+
}
79+
else if (flag == null) {
80+
if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
81+
// inner bean with other scope - can't reliably process events
82+
logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +
83+
"but is not reachable for event multicasting by its containing ApplicationContext " +
84+
"because it does not have singleton scope. Only top-level listener beans are allowed " +
85+
"to be of non-singleton scope.");
86+
}
87+
this.singletonNames.put(beanName, Boolean.FALSE);
88+
}
89+
}
90+
return bean;
91+
}
92+
93+
@Override
94+
public void postProcessBeforeDestruction(Object bean, String beanName) {
95+
if (bean instanceof ApplicationListener) {
96+
ApplicationEventMulticaster multicaster = this.applicationContext.getApplicationEventMulticaster();
97+
multicaster.removeApplicationListener((ApplicationListener<?>) bean);
98+
multicaster.removeApplicationListenerBean(beanName);
99+
}
100+
}
101+
102+
@Override
103+
public boolean requiresDestruction(Object bean) {
104+
return (bean instanceof ApplicationListener);
105+
}
106+
107+
108+
@Override
109+
public boolean equals(Object other) {
110+
return (this == other || (other instanceof ApplicationListenerDetector &&
111+
this.applicationContext == ((ApplicationListenerDetector) other).applicationContext));
112+
}
113+
114+
@Override
115+
public int hashCode() {
116+
return this.applicationContext.hashCode();
117+
}
118+
119+
}

spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java

Lines changed: 2 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@
2323
import java.util.HashSet;
2424
import java.util.LinkedList;
2525
import java.util.List;
26-
import java.util.Map;
2726
import java.util.Set;
28-
import java.util.concurrent.ConcurrentHashMap;
2927

3028
import org.apache.commons.logging.Log;
3129
import org.apache.commons.logging.LogFactory;
@@ -34,14 +32,11 @@
3432
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
3533
import org.springframework.beans.factory.config.BeanPostProcessor;
3634
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
37-
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
3835
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3936
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
4037
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
4138
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
4239
import org.springframework.beans.factory.support.RootBeanDefinition;
43-
import org.springframework.context.ApplicationListener;
44-
import org.springframework.context.event.ApplicationEventMulticaster;
4540
import org.springframework.core.OrderComparator;
4641
import org.springframework.core.Ordered;
4742
import org.springframework.core.PriorityOrdered;
@@ -249,6 +244,8 @@ else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
249244
sortPostProcessors(beanFactory, internalPostProcessors);
250245
registerBeanPostProcessors(beanFactory, internalPostProcessors);
251246

247+
// Re-register post-processor for detecting inner beans as ApplicationListeners,
248+
// moving it to the end of the processor chain (for picking up proxies etc).
252249
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
253250
}
254251

@@ -342,78 +339,4 @@ private boolean isInfrastructureBean(String beanName) {
342339
}
343340
}
344341

345-
346-
/**
347-
* {@code BeanPostProcessor} that detects beans which implement the {@code ApplicationListener}
348-
* interface. This catches beans that can't reliably be detected by {@code getBeanNamesForType}
349-
* and related operations which only work against top-level beans.
350-
*
351-
* <p>With standard Java serialization, this post-processor won't get serialized as part of
352-
* {@code DisposableBeanAdapter} to begin with. However, with alternative serialization
353-
* mechanisms, {@code DisposableBeanAdapter.writeReplace} might not get used at all, so we
354-
* defensively mark this post-processor's field state as {@code transient}.
355-
*/
356-
private static class ApplicationListenerDetector
357-
implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor {
358-
359-
private static final Log logger = LogFactory.getLog(ApplicationListenerDetector.class);
360-
361-
private transient final AbstractApplicationContext applicationContext;
362-
363-
private transient final Map<String, Boolean> singletonNames = new ConcurrentHashMap<String, Boolean>(256);
364-
365-
public ApplicationListenerDetector(AbstractApplicationContext applicationContext) {
366-
this.applicationContext = applicationContext;
367-
}
368-
369-
@Override
370-
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
371-
if (this.applicationContext != null && beanDefinition.isSingleton()) {
372-
this.singletonNames.put(beanName, Boolean.TRUE);
373-
}
374-
}
375-
376-
@Override
377-
public Object postProcessBeforeInitialization(Object bean, String beanName) {
378-
return bean;
379-
}
380-
381-
@Override
382-
public Object postProcessAfterInitialization(Object bean, String beanName) {
383-
if (this.applicationContext != null && bean instanceof ApplicationListener) {
384-
// potentially not detected as a listener by getBeanNamesForType retrieval
385-
Boolean flag = this.singletonNames.get(beanName);
386-
if (Boolean.TRUE.equals(flag)) {
387-
// singleton bean (top-level or inner): register on the fly
388-
this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
389-
}
390-
else if (flag == null) {
391-
if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
392-
// inner bean with other scope - can't reliably process events
393-
logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +
394-
"but is not reachable for event multicasting by its containing ApplicationContext " +
395-
"because it does not have singleton scope. Only top-level listener beans are allowed " +
396-
"to be of non-singleton scope.");
397-
}
398-
this.singletonNames.put(beanName, Boolean.FALSE);
399-
}
400-
}
401-
return bean;
402-
}
403-
404-
@Override
405-
public void postProcessBeforeDestruction(Object bean, String beanName) {
406-
if (bean instanceof ApplicationListener) {
407-
ApplicationEventMulticaster multicaster = this.applicationContext.getApplicationEventMulticaster();
408-
multicaster.removeApplicationListener((ApplicationListener<?>) bean);
409-
multicaster.removeApplicationListenerBean(beanName);
410-
}
411-
}
412-
413-
@Override
414-
public boolean requiresDestruction(Object bean) {
415-
return (bean instanceof ApplicationListener);
416-
}
417-
}
418-
419342
}

spring-context/src/test/java/org/springframework/context/support/BeanFactoryPostProcessorTests.java

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,8 +28,12 @@
2828
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
2929
import org.springframework.beans.factory.support.RootBeanDefinition;
3030
import org.springframework.context.ApplicationContext;
31+
import org.springframework.context.ApplicationEvent;
32+
import org.springframework.context.ApplicationListener;
33+
import org.springframework.context.event.ContextRefreshedEvent;
3134
import org.springframework.core.PriorityOrdered;
3235
import org.springframework.tests.sample.beans.TestBean;
36+
import org.springframework.util.Assert;
3337

3438
import static org.junit.Assert.*;
3539

@@ -119,6 +123,24 @@ public void testBeanDefinitionRegistryPostProcessorRegisteringAnother() throws E
119123
assertTrue(ac.getBean(TestBeanFactoryPostProcessor.class).wasCalled);
120124
}
121125

126+
@Test
127+
public void testBeanFactoryPostProcessorAsApplicationListener() {
128+
StaticApplicationContext ac = new StaticApplicationContext();
129+
ac.registerBeanDefinition("bfpp", new RootBeanDefinition(ListeningBeanFactoryPostProcessor.class));
130+
ac.refresh();
131+
assertTrue(ac.getBean(ListeningBeanFactoryPostProcessor.class).received instanceof ContextRefreshedEvent);
132+
}
133+
134+
@Test
135+
public void testBeanFactoryPostProcessorWithInnerBeanAsApplicationListener() {
136+
StaticApplicationContext ac = new StaticApplicationContext();
137+
RootBeanDefinition rbd = new RootBeanDefinition(NestingBeanFactoryPostProcessor.class);
138+
rbd.getPropertyValues().add("listeningBean", new RootBeanDefinition(ListeningBean.class));
139+
ac.registerBeanDefinition("bfpp", rbd);
140+
ac.refresh();
141+
assertTrue(ac.getBean(NestingBeanFactoryPostProcessor.class).getListeningBean().received instanceof ContextRefreshedEvent);
142+
}
143+
122144

123145
public static class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
124146

@@ -170,4 +192,50 @@ public int getOrder() {
170192
}
171193
}
172194

195+
196+
public static class ListeningBeanFactoryPostProcessor implements BeanFactoryPostProcessor, ApplicationListener {
197+
198+
public ApplicationEvent received;
199+
200+
@Override
201+
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
202+
}
203+
204+
@Override
205+
public void onApplicationEvent(ApplicationEvent event) {
206+
Assert.state(this.received == null, "Just one ContextRefreshedEvent expected");
207+
this.received = event;
208+
}
209+
}
210+
211+
212+
public static class ListeningBean implements ApplicationListener {
213+
214+
public ApplicationEvent received;
215+
216+
@Override
217+
public void onApplicationEvent(ApplicationEvent event) {
218+
Assert.state(this.received == null, "Just one ContextRefreshedEvent expected");
219+
this.received = event;
220+
}
221+
}
222+
223+
224+
public static class NestingBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
225+
226+
private ListeningBean listeningBean;
227+
228+
public void setListeningBean(ListeningBean listeningBean) {
229+
this.listeningBean = listeningBean;
230+
}
231+
232+
public ListeningBean getListeningBean() {
233+
return listeningBean;
234+
}
235+
236+
@Override
237+
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
238+
}
239+
}
240+
173241
}

0 commit comments

Comments
 (0)