Revise event multicaster locking for non-synchronized retriever caching

Closes gh-25799
This commit is contained in:
Juergen Hoeller 2020-09-25 11:24:26 +02:00
parent d9da663f6d
commit c83f6adc24
2 changed files with 180 additions and 56 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 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.
@ -63,9 +63,9 @@ import org.springframework.util.ObjectUtils;
public abstract class AbstractApplicationEventMulticaster public abstract class AbstractApplicationEventMulticaster
implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware { implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
private final ListenerRetriever defaultRetriever = new ListenerRetriever(false); private final DefaultListenerRetriever defaultRetriever = new DefaultListenerRetriever();
final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64); final Map<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
@Nullable @Nullable
private ClassLoader beanClassLoader; private ClassLoader beanClassLoader;
@ -73,8 +73,6 @@ public abstract class AbstractApplicationEventMulticaster
@Nullable @Nullable
private ConfigurableBeanFactory beanFactory; private ConfigurableBeanFactory beanFactory;
private Object retrievalMutex = this.defaultRetriever;
@Override @Override
public void setBeanClassLoader(ClassLoader classLoader) { public void setBeanClassLoader(ClassLoader classLoader) {
@ -90,7 +88,6 @@ public abstract class AbstractApplicationEventMulticaster
if (this.beanClassLoader == null) { if (this.beanClassLoader == null) {
this.beanClassLoader = this.beanFactory.getBeanClassLoader(); this.beanClassLoader = this.beanFactory.getBeanClassLoader();
} }
this.retrievalMutex = this.beanFactory.getSingletonMutex();
} }
private ConfigurableBeanFactory getBeanFactory() { private ConfigurableBeanFactory getBeanFactory() {
@ -104,7 +101,7 @@ public abstract class AbstractApplicationEventMulticaster
@Override @Override
public void addApplicationListener(ApplicationListener<?> listener) { public void addApplicationListener(ApplicationListener<?> listener) {
synchronized (this.retrievalMutex) { synchronized (this.defaultRetriever) {
// Explicitly remove target for a proxy, if registered already, // Explicitly remove target for a proxy, if registered already,
// in order to avoid double invocations of the same listener. // in order to avoid double invocations of the same listener.
Object singletonTarget = AopProxyUtils.getSingletonTarget(listener); Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
@ -118,7 +115,7 @@ public abstract class AbstractApplicationEventMulticaster
@Override @Override
public void addApplicationListenerBean(String listenerBeanName) { public void addApplicationListenerBean(String listenerBeanName) {
synchronized (this.retrievalMutex) { synchronized (this.defaultRetriever) {
this.defaultRetriever.applicationListenerBeans.add(listenerBeanName); this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);
this.retrieverCache.clear(); this.retrieverCache.clear();
} }
@ -126,7 +123,7 @@ public abstract class AbstractApplicationEventMulticaster
@Override @Override
public void removeApplicationListener(ApplicationListener<?> listener) { public void removeApplicationListener(ApplicationListener<?> listener) {
synchronized (this.retrievalMutex) { synchronized (this.defaultRetriever) {
this.defaultRetriever.applicationListeners.remove(listener); this.defaultRetriever.applicationListeners.remove(listener);
this.retrieverCache.clear(); this.retrieverCache.clear();
} }
@ -134,7 +131,7 @@ public abstract class AbstractApplicationEventMulticaster
@Override @Override
public void removeApplicationListenerBean(String listenerBeanName) { public void removeApplicationListenerBean(String listenerBeanName) {
synchronized (this.retrievalMutex) { synchronized (this.defaultRetriever) {
this.defaultRetriever.applicationListenerBeans.remove(listenerBeanName); this.defaultRetriever.applicationListenerBeans.remove(listenerBeanName);
this.retrieverCache.clear(); this.retrieverCache.clear();
} }
@ -142,7 +139,7 @@ public abstract class AbstractApplicationEventMulticaster
@Override @Override
public void removeAllListeners() { public void removeAllListeners() {
synchronized (this.retrievalMutex) { synchronized (this.defaultRetriever) {
this.defaultRetriever.applicationListeners.clear(); this.defaultRetriever.applicationListeners.clear();
this.defaultRetriever.applicationListenerBeans.clear(); this.defaultRetriever.applicationListenerBeans.clear();
this.retrieverCache.clear(); this.retrieverCache.clear();
@ -156,7 +153,7 @@ public abstract class AbstractApplicationEventMulticaster
* @see org.springframework.context.ApplicationListener * @see org.springframework.context.ApplicationListener
*/ */
protected Collection<ApplicationListener<?>> getApplicationListeners() { protected Collection<ApplicationListener<?>> getApplicationListeners() {
synchronized (this.retrievalMutex) { synchronized (this.defaultRetriever) {
return this.defaultRetriever.getApplicationListeners(); return this.defaultRetriever.getApplicationListeners();
} }
} }
@ -177,32 +174,34 @@ public abstract class AbstractApplicationEventMulticaster
Class<?> sourceType = (source != null ? source.getClass() : null); Class<?> sourceType = (source != null ? source.getClass() : null);
ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType); ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
// Quick check for existing entry on ConcurrentHashMap... // Potential new retriever to populate
ListenerRetriever retriever = this.retrieverCache.get(cacheKey); CachedListenerRetriever newRetriever = null;
if (retriever != null) {
return retriever.getApplicationListeners();
}
if (this.beanClassLoader == null || // Quick check for existing entry on ConcurrentHashMap
(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) && CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);
(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) { if (existingRetriever == null) {
// Fully synchronized building and caching of a ListenerRetriever // Caching a new ListenerRetriever if possible
synchronized (this.retrievalMutex) { if (this.beanClassLoader == null ||
retriever = this.retrieverCache.get(cacheKey); (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
if (retriever != null) { (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
return retriever.getApplicationListeners(); newRetriever = new CachedListenerRetriever();
existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
if (existingRetriever != null) {
newRetriever = null; // no need to populate it in retrieveApplicationListeners
} }
retriever = new ListenerRetriever(true);
Collection<ApplicationListener<?>> listeners =
retrieveApplicationListeners(eventType, sourceType, retriever);
this.retrieverCache.put(cacheKey, retriever);
return listeners;
} }
} }
else {
// No ListenerRetriever caching -> no synchronization necessary if (existingRetriever != null) {
return retrieveApplicationListeners(eventType, sourceType, null); Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
if (result != null) {
return result;
}
// If result is null, the existing retriever is not fully populated yet by another thread.
// Proceed like caching wasn't possible for this current local attempt.
} }
return retrieveApplicationListeners(eventType, sourceType, newRetriever);
} }
/** /**
@ -213,12 +212,15 @@ public abstract class AbstractApplicationEventMulticaster
* @return the pre-filtered list of application listeners for the given event and source type * @return the pre-filtered list of application listeners for the given event and source type
*/ */
private Collection<ApplicationListener<?>> retrieveApplicationListeners( private Collection<ApplicationListener<?>> retrieveApplicationListeners(
ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) { ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {
List<ApplicationListener<?>> allListeners = new ArrayList<>(); List<ApplicationListener<?>> allListeners = new ArrayList<>();
Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);
Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);
Set<ApplicationListener<?>> listeners; Set<ApplicationListener<?>> listeners;
Set<String> listenerBeans; Set<String> listenerBeans;
synchronized (this.retrievalMutex) { synchronized (this.defaultRetriever) {
listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners); listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans); listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
} }
@ -228,7 +230,7 @@ public abstract class AbstractApplicationEventMulticaster
for (ApplicationListener<?> listener : listeners) { for (ApplicationListener<?> listener : listeners) {
if (supportsEvent(listener, eventType, sourceType)) { if (supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) { if (retriever != null) {
retriever.applicationListeners.add(listener); filteredListeners.add(listener);
} }
allListeners.add(listener); allListeners.add(listener);
} }
@ -246,10 +248,10 @@ public abstract class AbstractApplicationEventMulticaster
if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) { if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) { if (retriever != null) {
if (beanFactory.isSingleton(listenerBeanName)) { if (beanFactory.isSingleton(listenerBeanName)) {
retriever.applicationListeners.add(listener); filteredListeners.add(listener);
} }
else { else {
retriever.applicationListenerBeans.add(listenerBeanName); filteredListenerBeans.add(listenerBeanName);
} }
} }
allListeners.add(listener); allListeners.add(listener);
@ -261,7 +263,7 @@ public abstract class AbstractApplicationEventMulticaster
// BeanDefinition metadata (e.g. factory method generics) above. // BeanDefinition metadata (e.g. factory method generics) above.
Object listener = beanFactory.getSingleton(listenerBeanName); Object listener = beanFactory.getSingleton(listenerBeanName);
if (retriever != null) { if (retriever != null) {
retriever.applicationListeners.remove(listener); filteredListeners.remove(listener);
} }
allListeners.remove(listener); allListeners.remove(listener);
} }
@ -274,9 +276,15 @@ public abstract class AbstractApplicationEventMulticaster
} }
AnnotationAwareOrderComparator.sort(allListeners); AnnotationAwareOrderComparator.sort(allListeners);
if (retriever != null && retriever.applicationListenerBeans.isEmpty()) { if (retriever != null) {
retriever.applicationListeners.clear(); if (filteredListenerBeans.isEmpty()) {
retriever.applicationListeners.addAll(allListeners); retriever.applicationListeners = new LinkedHashSet<>(allListeners);
retriever.applicationListenerBeans = filteredListenerBeans;
}
else {
retriever.applicationListeners = filteredListeners;
retriever.applicationListenerBeans = filteredListenerBeans;
}
} }
return allListeners; return allListeners;
} }
@ -415,18 +423,55 @@ public abstract class AbstractApplicationEventMulticaster
* allowing for efficient retrieval of pre-filtered listeners. * allowing for efficient retrieval of pre-filtered listeners.
* <p>An instance of this helper gets cached per event type and source type. * <p>An instance of this helper gets cached per event type and source type.
*/ */
private class ListenerRetriever { private class CachedListenerRetriever {
@Nullable
public volatile Set<ApplicationListener<?>> applicationListeners;
@Nullable
public volatile Set<String> applicationListenerBeans;
@Nullable
public Collection<ApplicationListener<?>> getApplicationListeners() {
Set<ApplicationListener<?>> applicationListeners = this.applicationListeners;
Set<String> applicationListenerBeans = this.applicationListenerBeans;
if (applicationListeners == null || applicationListenerBeans == null) {
// Not fully populated yet
return null;
}
List<ApplicationListener<?>> allListeners = new ArrayList<>(
applicationListeners.size() + applicationListenerBeans.size());
allListeners.addAll(applicationListeners);
if (!applicationListenerBeans.isEmpty()) {
BeanFactory beanFactory = getBeanFactory();
for (String listenerBeanName : applicationListenerBeans) {
try {
allListeners.add(beanFactory.getBean(listenerBeanName, ApplicationListener.class));
}
catch (NoSuchBeanDefinitionException ex) {
// Singleton listener instance (without backing bean definition) disappeared -
// probably in the middle of the destruction phase
}
}
}
if (!applicationListenerBeans.isEmpty()) {
AnnotationAwareOrderComparator.sort(allListeners);
}
return allListeners;
}
}
/**
* Helper class that encapsulates a general set of target listeners.
*/
private class DefaultListenerRetriever {
public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>(); public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
public final Set<String> applicationListenerBeans = new LinkedHashSet<>(); public final Set<String> applicationListenerBeans = new LinkedHashSet<>();
private final boolean preFiltered;
public ListenerRetriever(boolean preFiltered) {
this.preFiltered = preFiltered;
}
public Collection<ApplicationListener<?>> getApplicationListeners() { public Collection<ApplicationListener<?>> getApplicationListeners() {
List<ApplicationListener<?>> allListeners = new ArrayList<>( List<ApplicationListener<?>> allListeners = new ArrayList<>(
this.applicationListeners.size() + this.applicationListenerBeans.size()); this.applicationListeners.size() + this.applicationListenerBeans.size());
@ -435,8 +480,9 @@ public abstract class AbstractApplicationEventMulticaster
BeanFactory beanFactory = getBeanFactory(); BeanFactory beanFactory = getBeanFactory();
for (String listenerBeanName : this.applicationListenerBeans) { for (String listenerBeanName : this.applicationListenerBeans) {
try { try {
ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); ApplicationListener<?> listener =
if (this.preFiltered || !allListeners.contains(listener)) { beanFactory.getBean(listenerBeanName, ApplicationListener.class);
if (!allListeners.contains(listener)) {
allListeners.add(listener); allListeners.add(listener);
} }
} }
@ -446,9 +492,7 @@ public abstract class AbstractApplicationEventMulticaster
} }
} }
} }
if (!this.preFiltered || !this.applicationListenerBeans.isEmpty()) { AnnotationAwareOrderComparator.sort(allListeners);
AnnotationAwareOrderComparator.sort(allListeners);
}
return allListeners; return allListeners;
} }
} }

View File

@ -16,8 +16,8 @@
package org.springframework.context.event; package org.springframework.context.event;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference;
@ -35,6 +36,8 @@ import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.PayloadApplicationEvent; import org.springframework.context.PayloadApplicationEvent;
import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.AbstractApplicationContext;
@ -374,6 +377,19 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
assertThat(MyNonSingletonListener.seenEvents.contains(event4)).isTrue(); assertThat(MyNonSingletonListener.seenEvents.contains(event4)).isTrue();
MyNonSingletonListener.seenEvents.clear(); MyNonSingletonListener.seenEvents.clear();
context.publishEvent(event1);
context.publishEvent(event2);
context.publishEvent(event3);
context.publishEvent(event4);
assertThat(MyNonSingletonListener.seenEvents.contains(event1)).isTrue();
assertThat(MyNonSingletonListener.seenEvents.contains(event2)).isTrue();
assertThat(MyNonSingletonListener.seenEvents.contains(event3)).isTrue();
assertThat(MyNonSingletonListener.seenEvents.contains(event4)).isTrue();
MyNonSingletonListener.seenEvents.clear();
AbstractApplicationEventMulticaster multicaster = context.getBean(AbstractApplicationEventMulticaster.class);
assertThat(multicaster.retrieverCache.size()).isEqualTo(3);
context.close(); context.close();
} }
@ -516,6 +532,36 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
context.close(); context.close();
} }
@Test
public void initMethodPublishesEvent() {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBeanDefinition("listener", new RootBeanDefinition(BeanThatListens.class));
context.registerBeanDefinition("messageSource", new RootBeanDefinition(StaticMessageSource.class));
context.registerBeanDefinition("initMethod", new RootBeanDefinition(EventPublishingInitMethod.class));
context.refresh();
context.publishEvent(new MyEvent(this));
BeanThatListens listener = context.getBean(BeanThatListens.class);
assertThat(listener.getEventCount()).isEqualTo(3);
context.close();
}
@Test
public void initMethodPublishesAsyncEvent() {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBeanDefinition("listener", new RootBeanDefinition(BeanThatListens.class));
context.registerBeanDefinition("messageSource", new RootBeanDefinition(StaticMessageSource.class));
context.registerBeanDefinition("initMethod", new RootBeanDefinition(AsyncEventPublishingInitMethod.class));
context.refresh();
context.publishEvent(new MyEvent(this));
BeanThatListens listener = context.getBean(BeanThatListens.class);
assertThat(listener.getEventCount()).isEqualTo(3);
context.close();
}
@SuppressWarnings("serial") @SuppressWarnings("serial")
public static class MyEvent extends ApplicationEvent { public static class MyEvent extends ApplicationEvent {
@ -537,7 +583,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
public static class MyOrderedListener1 implements ApplicationListener<ApplicationEvent>, Ordered { public static class MyOrderedListener1 implements ApplicationListener<ApplicationEvent>, Ordered {
public final List<ApplicationEvent> seenEvents = new LinkedList<>(); public final List<ApplicationEvent> seenEvents = new ArrayList<>();
@Override @Override
public void onApplicationEvent(ApplicationEvent event) { public void onApplicationEvent(ApplicationEvent event) {
@ -652,4 +698,38 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
} }
} }
public static class EventPublishingInitMethod implements ApplicationEventPublisherAware, InitializingBean {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
@Override
public void afterPropertiesSet() throws Exception {
this.publisher.publishEvent(new MyEvent(this));
}
}
public static class AsyncEventPublishingInitMethod implements ApplicationEventPublisherAware, InitializingBean {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
@Override
public void afterPropertiesSet() throws Exception {
Thread thread = new Thread(() -> this.publisher.publishEvent(new MyEvent(this)));
thread.start();
thread.join();
}
}
} }