Skip to content

Commit c83f6ad

Browse files
committed
Revise event multicaster locking for non-synchronized retriever caching
Closes gh-25799
1 parent d9da663 commit c83f6ad

File tree

2 files changed

+180
-56
lines changed

2 files changed

+180
-56
lines changed

spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java

+98-54
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -63,18 +63,16 @@
6363
public abstract class AbstractApplicationEventMulticaster
6464
implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
6565

66-
private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);
66+
private final DefaultListenerRetriever defaultRetriever = new DefaultListenerRetriever();
6767

68-
final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
68+
final Map<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
6969

7070
@Nullable
7171
private ClassLoader beanClassLoader;
7272

7373
@Nullable
7474
private ConfigurableBeanFactory beanFactory;
7575

76-
private Object retrievalMutex = this.defaultRetriever;
77-
7876

7977
@Override
8078
public void setBeanClassLoader(ClassLoader classLoader) {
@@ -90,7 +88,6 @@ public void setBeanFactory(BeanFactory beanFactory) {
9088
if (this.beanClassLoader == null) {
9189
this.beanClassLoader = this.beanFactory.getBeanClassLoader();
9290
}
93-
this.retrievalMutex = this.beanFactory.getSingletonMutex();
9491
}
9592

9693
private ConfigurableBeanFactory getBeanFactory() {
@@ -104,7 +101,7 @@ private ConfigurableBeanFactory getBeanFactory() {
104101

105102
@Override
106103
public void addApplicationListener(ApplicationListener<?> listener) {
107-
synchronized (this.retrievalMutex) {
104+
synchronized (this.defaultRetriever) {
108105
// Explicitly remove target for a proxy, if registered already,
109106
// in order to avoid double invocations of the same listener.
110107
Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
@@ -118,31 +115,31 @@ public void addApplicationListener(ApplicationListener<?> listener) {
118115

119116
@Override
120117
public void addApplicationListenerBean(String listenerBeanName) {
121-
synchronized (this.retrievalMutex) {
118+
synchronized (this.defaultRetriever) {
122119
this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);
123120
this.retrieverCache.clear();
124121
}
125122
}
126123

127124
@Override
128125
public void removeApplicationListener(ApplicationListener<?> listener) {
129-
synchronized (this.retrievalMutex) {
126+
synchronized (this.defaultRetriever) {
130127
this.defaultRetriever.applicationListeners.remove(listener);
131128
this.retrieverCache.clear();
132129
}
133130
}
134131

135132
@Override
136133
public void removeApplicationListenerBean(String listenerBeanName) {
137-
synchronized (this.retrievalMutex) {
134+
synchronized (this.defaultRetriever) {
138135
this.defaultRetriever.applicationListenerBeans.remove(listenerBeanName);
139136
this.retrieverCache.clear();
140137
}
141138
}
142139

143140
@Override
144141
public void removeAllListeners() {
145-
synchronized (this.retrievalMutex) {
142+
synchronized (this.defaultRetriever) {
146143
this.defaultRetriever.applicationListeners.clear();
147144
this.defaultRetriever.applicationListenerBeans.clear();
148145
this.retrieverCache.clear();
@@ -156,7 +153,7 @@ public void removeAllListeners() {
156153
* @see org.springframework.context.ApplicationListener
157154
*/
158155
protected Collection<ApplicationListener<?>> getApplicationListeners() {
159-
synchronized (this.retrievalMutex) {
156+
synchronized (this.defaultRetriever) {
160157
return this.defaultRetriever.getApplicationListeners();
161158
}
162159
}
@@ -177,32 +174,34 @@ protected Collection<ApplicationListener<?>> getApplicationListeners(
177174
Class<?> sourceType = (source != null ? source.getClass() : null);
178175
ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
179176

180-
// Quick check for existing entry on ConcurrentHashMap...
181-
ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
182-
if (retriever != null) {
183-
return retriever.getApplicationListeners();
184-
}
185-
186-
if (this.beanClassLoader == null ||
187-
(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
188-
(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
189-
// Fully synchronized building and caching of a ListenerRetriever
190-
synchronized (this.retrievalMutex) {
191-
retriever = this.retrieverCache.get(cacheKey);
192-
if (retriever != null) {
193-
return retriever.getApplicationListeners();
177+
// Potential new retriever to populate
178+
CachedListenerRetriever newRetriever = null;
179+
180+
// Quick check for existing entry on ConcurrentHashMap
181+
CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);
182+
if (existingRetriever == null) {
183+
// Caching a new ListenerRetriever if possible
184+
if (this.beanClassLoader == null ||
185+
(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
186+
(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
187+
newRetriever = new CachedListenerRetriever();
188+
existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
189+
if (existingRetriever != null) {
190+
newRetriever = null; // no need to populate it in retrieveApplicationListeners
194191
}
195-
retriever = new ListenerRetriever(true);
196-
Collection<ApplicationListener<?>> listeners =
197-
retrieveApplicationListeners(eventType, sourceType, retriever);
198-
this.retrieverCache.put(cacheKey, retriever);
199-
return listeners;
200192
}
201193
}
202-
else {
203-
// No ListenerRetriever caching -> no synchronization necessary
204-
return retrieveApplicationListeners(eventType, sourceType, null);
194+
195+
if (existingRetriever != null) {
196+
Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
197+
if (result != null) {
198+
return result;
199+
}
200+
// If result is null, the existing retriever is not fully populated yet by another thread.
201+
// Proceed like caching wasn't possible for this current local attempt.
205202
}
203+
204+
return retrieveApplicationListeners(eventType, sourceType, newRetriever);
206205
}
207206

208207
/**
@@ -213,12 +212,15 @@ protected Collection<ApplicationListener<?>> getApplicationListeners(
213212
* @return the pre-filtered list of application listeners for the given event and source type
214213
*/
215214
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
216-
ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {
215+
ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {
217216

218217
List<ApplicationListener<?>> allListeners = new ArrayList<>();
218+
Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);
219+
Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);
220+
219221
Set<ApplicationListener<?>> listeners;
220222
Set<String> listenerBeans;
221-
synchronized (this.retrievalMutex) {
223+
synchronized (this.defaultRetriever) {
222224
listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
223225
listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
224226
}
@@ -228,7 +230,7 @@ private Collection<ApplicationListener<?>> retrieveApplicationListeners(
228230
for (ApplicationListener<?> listener : listeners) {
229231
if (supportsEvent(listener, eventType, sourceType)) {
230232
if (retriever != null) {
231-
retriever.applicationListeners.add(listener);
233+
filteredListeners.add(listener);
232234
}
233235
allListeners.add(listener);
234236
}
@@ -246,10 +248,10 @@ private Collection<ApplicationListener<?>> retrieveApplicationListeners(
246248
if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
247249
if (retriever != null) {
248250
if (beanFactory.isSingleton(listenerBeanName)) {
249-
retriever.applicationListeners.add(listener);
251+
filteredListeners.add(listener);
250252
}
251253
else {
252-
retriever.applicationListenerBeans.add(listenerBeanName);
254+
filteredListenerBeans.add(listenerBeanName);
253255
}
254256
}
255257
allListeners.add(listener);
@@ -261,7 +263,7 @@ private Collection<ApplicationListener<?>> retrieveApplicationListeners(
261263
// BeanDefinition metadata (e.g. factory method generics) above.
262264
Object listener = beanFactory.getSingleton(listenerBeanName);
263265
if (retriever != null) {
264-
retriever.applicationListeners.remove(listener);
266+
filteredListeners.remove(listener);
265267
}
266268
allListeners.remove(listener);
267269
}
@@ -274,9 +276,15 @@ private Collection<ApplicationListener<?>> retrieveApplicationListeners(
274276
}
275277

276278
AnnotationAwareOrderComparator.sort(allListeners);
277-
if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
278-
retriever.applicationListeners.clear();
279-
retriever.applicationListeners.addAll(allListeners);
279+
if (retriever != null) {
280+
if (filteredListenerBeans.isEmpty()) {
281+
retriever.applicationListeners = new LinkedHashSet<>(allListeners);
282+
retriever.applicationListenerBeans = filteredListenerBeans;
283+
}
284+
else {
285+
retriever.applicationListeners = filteredListeners;
286+
retriever.applicationListenerBeans = filteredListenerBeans;
287+
}
280288
}
281289
return allListeners;
282290
}
@@ -415,17 +423,54 @@ public int compareTo(ListenerCacheKey other) {
415423
* allowing for efficient retrieval of pre-filtered listeners.
416424
* <p>An instance of this helper gets cached per event type and source type.
417425
*/
418-
private class ListenerRetriever {
426+
private class CachedListenerRetriever {
419427

420-
public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
428+
@Nullable
429+
public volatile Set<ApplicationListener<?>> applicationListeners;
421430

422-
public final Set<String> applicationListenerBeans = new LinkedHashSet<>();
431+
@Nullable
432+
public volatile Set<String> applicationListenerBeans;
423433

424-
private final boolean preFiltered;
434+
@Nullable
435+
public Collection<ApplicationListener<?>> getApplicationListeners() {
436+
Set<ApplicationListener<?>> applicationListeners = this.applicationListeners;
437+
Set<String> applicationListenerBeans = this.applicationListenerBeans;
438+
if (applicationListeners == null || applicationListenerBeans == null) {
439+
// Not fully populated yet
440+
return null;
441+
}
425442

426-
public ListenerRetriever(boolean preFiltered) {
427-
this.preFiltered = preFiltered;
443+
List<ApplicationListener<?>> allListeners = new ArrayList<>(
444+
applicationListeners.size() + applicationListenerBeans.size());
445+
allListeners.addAll(applicationListeners);
446+
if (!applicationListenerBeans.isEmpty()) {
447+
BeanFactory beanFactory = getBeanFactory();
448+
for (String listenerBeanName : applicationListenerBeans) {
449+
try {
450+
allListeners.add(beanFactory.getBean(listenerBeanName, ApplicationListener.class));
451+
}
452+
catch (NoSuchBeanDefinitionException ex) {
453+
// Singleton listener instance (without backing bean definition) disappeared -
454+
// probably in the middle of the destruction phase
455+
}
456+
}
457+
}
458+
if (!applicationListenerBeans.isEmpty()) {
459+
AnnotationAwareOrderComparator.sort(allListeners);
460+
}
461+
return allListeners;
428462
}
463+
}
464+
465+
466+
/**
467+
* Helper class that encapsulates a general set of target listeners.
468+
*/
469+
private class DefaultListenerRetriever {
470+
471+
public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
472+
473+
public final Set<String> applicationListenerBeans = new LinkedHashSet<>();
429474

430475
public Collection<ApplicationListener<?>> getApplicationListeners() {
431476
List<ApplicationListener<?>> allListeners = new ArrayList<>(
@@ -435,8 +480,9 @@ public Collection<ApplicationListener<?>> getApplicationListeners() {
435480
BeanFactory beanFactory = getBeanFactory();
436481
for (String listenerBeanName : this.applicationListenerBeans) {
437482
try {
438-
ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
439-
if (this.preFiltered || !allListeners.contains(listener)) {
483+
ApplicationListener<?> listener =
484+
beanFactory.getBean(listenerBeanName, ApplicationListener.class);
485+
if (!allListeners.contains(listener)) {
440486
allListeners.add(listener);
441487
}
442488
}
@@ -446,9 +492,7 @@ public Collection<ApplicationListener<?>> getApplicationListeners() {
446492
}
447493
}
448494
}
449-
if (!this.preFiltered || !this.applicationListenerBeans.isEmpty()) {
450-
AnnotationAwareOrderComparator.sort(allListeners);
451-
}
495+
AnnotationAwareOrderComparator.sort(allListeners);
452496
return allListeners;
453497
}
454498
}

0 commit comments

Comments
 (0)