/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.event;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;

Abstract implementation of the ApplicationEventMulticaster interface, providing the basic listener registration facility.

Doesn't permit multiple instances of the same listener by default, as it keeps listeners in a linked Set. The collection class used to hold ApplicationListener objects can be overridden through the "collectionClass" bean property.

Implementing ApplicationEventMulticaster's actual ApplicationEventMulticaster.multicastEvent method is left to subclasses. SimpleApplicationEventMulticaster simply multicasts all events to all registered listeners, invoking them in the calling thread. Alternative implementations could be more sophisticated in those respects.

Author:Juergen Hoeller, Stephane Nicoll
See Also:
Since:1.2.3
/** * Abstract implementation of the {@link ApplicationEventMulticaster} interface, * providing the basic listener registration facility. * * <p>Doesn't permit multiple instances of the same listener by default, * as it keeps listeners in a linked Set. The collection class used to hold * ApplicationListener objects can be overridden through the "collectionClass" * bean property. * * <p>Implementing ApplicationEventMulticaster's actual {@link #multicastEvent} method * is left to subclasses. {@link SimpleApplicationEventMulticaster} simply multicasts * all events to all registered listeners, invoking them in the calling thread. * Alternative implementations could be more sophisticated in those respects. * * @author Juergen Hoeller * @author Stephane Nicoll * @since 1.2.3 * @see #getApplicationListeners(ApplicationEvent, ResolvableType) * @see SimpleApplicationEventMulticaster */
public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware { private final ListenerRetriever defaultRetriever = new ListenerRetriever(false); final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64); @Nullable private ClassLoader beanClassLoader; @Nullable private BeanFactory beanFactory; private Object retrievalMutex = this.defaultRetriever; @Override public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } @Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; if (beanFactory instanceof ConfigurableBeanFactory) { ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory; if (this.beanClassLoader == null) { this.beanClassLoader = cbf.getBeanClassLoader(); } this.retrievalMutex = cbf.getSingletonMutex(); } } private BeanFactory getBeanFactory() { if (this.beanFactory == null) { throw new IllegalStateException("ApplicationEventMulticaster cannot retrieve listener beans " + "because it is not associated with a BeanFactory"); } return this.beanFactory; } @Override public void addApplicationListener(ApplicationListener<?> listener) { synchronized (this.retrievalMutex) { // Explicitly remove target for a proxy, if registered already, // in order to avoid double invocations of the same listener. Object singletonTarget = AopProxyUtils.getSingletonTarget(listener); if (singletonTarget instanceof ApplicationListener) { this.defaultRetriever.applicationListeners.remove(singletonTarget); } this.defaultRetriever.applicationListeners.add(listener); this.retrieverCache.clear(); } } @Override public void addApplicationListenerBean(String listenerBeanName) { synchronized (this.retrievalMutex) { this.defaultRetriever.applicationListenerBeans.add(listenerBeanName); this.retrieverCache.clear(); } } @Override public void removeApplicationListener(ApplicationListener<?> listener) { synchronized (this.retrievalMutex) { this.defaultRetriever.applicationListeners.remove(listener); this.retrieverCache.clear(); } } @Override public void removeApplicationListenerBean(String listenerBeanName) { synchronized (this.retrievalMutex) { this.defaultRetriever.applicationListenerBeans.remove(listenerBeanName); this.retrieverCache.clear(); } } @Override public void removeAllListeners() { synchronized (this.retrievalMutex) { this.defaultRetriever.applicationListeners.clear(); this.defaultRetriever.applicationListenerBeans.clear(); this.retrieverCache.clear(); } }
Return a Collection containing all ApplicationListeners.
See Also:
Returns:a Collection of ApplicationListeners
/** * Return a Collection containing all ApplicationListeners. * @return a Collection of ApplicationListeners * @see org.springframework.context.ApplicationListener */
protected Collection<ApplicationListener<?>> getApplicationListeners() { synchronized (this.retrievalMutex) { return this.defaultRetriever.getApplicationListeners(); } }
Return a Collection of ApplicationListeners matching the given event type. Non-matching listeners get excluded early.
Params:
  • event – the event to be propagated. Allows for excluding non-matching listeners early, based on cached matching information.
  • eventType – the event type
See Also:
Returns:a Collection of ApplicationListeners
/** * Return a Collection of ApplicationListeners matching the given * event type. Non-matching listeners get excluded early. * @param event the event to be propagated. Allows for excluding * non-matching listeners early, based on cached matching information. * @param eventType the event type * @return a Collection of ApplicationListeners * @see org.springframework.context.ApplicationListener */
protected Collection<ApplicationListener<?>> getApplicationListeners( ApplicationEvent event, ResolvableType eventType) { Object source = event.getSource(); Class<?> sourceType = (source != null ? source.getClass() : null); ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType); // Quick check for existing entry on ConcurrentHashMap... ListenerRetriever retriever = this.retrieverCache.get(cacheKey); if (retriever != null) { return retriever.getApplicationListeners(); } if (this.beanClassLoader == null || (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) && (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) { // Fully synchronized building and caching of a ListenerRetriever synchronized (this.retrievalMutex) { retriever = this.retrieverCache.get(cacheKey); if (retriever != null) { return retriever.getApplicationListeners(); } 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 return retrieveApplicationListeners(eventType, sourceType, null); } }
Actually retrieve the application listeners for the given event and source type.
Params:
  • eventType – the event type
  • sourceType – the event source type
  • retriever – the ListenerRetriever, if supposed to populate one (for caching purposes)
Returns:the pre-filtered list of application listeners for the given event and source type
/** * Actually retrieve the application listeners for the given event and source type. * @param eventType the event type * @param sourceType the event source type * @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes) * @return the pre-filtered list of application listeners for the given event and source type */
private Collection<ApplicationListener<?>> retrieveApplicationListeners( ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) { List<ApplicationListener<?>> allListeners = new ArrayList<>(); Set<ApplicationListener<?>> listeners; Set<String> listenerBeans; synchronized (this.retrievalMutex) { listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners); listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans); } for (ApplicationListener<?> listener : listeners) { if (supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { retriever.applicationListeners.add(listener); } allListeners.add(listener); } } if (!listenerBeans.isEmpty()) { BeanFactory beanFactory = getBeanFactory(); for (String listenerBeanName : listenerBeans) { try { Class<?> listenerType = beanFactory.getType(listenerBeanName); if (listenerType == null || supportsEvent(listenerType, eventType)) { ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { if (beanFactory.isSingleton(listenerBeanName)) { retriever.applicationListeners.add(listener); } else { retriever.applicationListenerBeans.add(listenerBeanName); } } allListeners.add(listener); } } } catch (NoSuchBeanDefinitionException ex) { // Singleton listener instance (without backing bean definition) disappeared - // probably in the middle of the destruction phase } } } AnnotationAwareOrderComparator.sort(allListeners); if (retriever != null && retriever.applicationListenerBeans.isEmpty()) { retriever.applicationListeners.clear(); retriever.applicationListeners.addAll(allListeners); } return allListeners; }
Filter a listener early through checking its generically declared event type before trying to instantiate it.

If this method returns true for a given listener as a first pass, the listener instance will get retrieved and fully evaluated through a supportsEvent(ApplicationListener<?>, ResolvableType, Class<?>) call afterwards.

Params:
  • listenerType – the listener's type as determined by the BeanFactory
  • eventType – the event type to check
Returns:whether the given listener should be included in the candidates for the given event type
/** * Filter a listener early through checking its generically declared event * type before trying to instantiate it. * <p>If this method returns {@code true} for a given listener as a first pass, * the listener instance will get retrieved and fully evaluated through a * {@link #supportsEvent(ApplicationListener,ResolvableType, Class)} call afterwards. * @param listenerType the listener's type as determined by the BeanFactory * @param eventType the event type to check * @return whether the given listener should be included in the candidates * for the given event type */
protected boolean supportsEvent(Class<?> listenerType, ResolvableType eventType) { if (GenericApplicationListener.class.isAssignableFrom(listenerType) || SmartApplicationListener.class.isAssignableFrom(listenerType)) { return true; } ResolvableType declaredEventType = GenericApplicationListenerAdapter.resolveDeclaredEventType(listenerType); return (declaredEventType == null || declaredEventType.isAssignableFrom(eventType)); }
Determine whether the given listener supports the given event.

The default implementation detects the SmartApplicationListener and GenericApplicationListener interfaces. In case of a standard ApplicationListener, a GenericApplicationListenerAdapter will be used to introspect the generically declared type of the target listener.

Params:
  • listener – the target listener to check
  • eventType – the event type to check against
  • sourceType – the source type to check against
Returns:whether the given listener should be included in the candidates for the given event type
/** * Determine whether the given listener supports the given event. * <p>The default implementation detects the {@link SmartApplicationListener} * and {@link GenericApplicationListener} interfaces. In case of a standard * {@link ApplicationListener}, a {@link GenericApplicationListenerAdapter} * will be used to introspect the generically declared type of the target listener. * @param listener the target listener to check * @param eventType the event type to check against * @param sourceType the source type to check against * @return whether the given listener should be included in the candidates * for the given event type */
protected boolean supportsEvent( ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) { GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ? (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener)); return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType)); }
Cache key for ListenerRetrievers, based on event type and source type.
/** * Cache key for ListenerRetrievers, based on event type and source type. */
private static final class ListenerCacheKey implements Comparable<ListenerCacheKey> { private final ResolvableType eventType; @Nullable private final Class<?> sourceType; public ListenerCacheKey(ResolvableType eventType, @Nullable Class<?> sourceType) { Assert.notNull(eventType, "Event type must not be null"); this.eventType = eventType; this.sourceType = sourceType; } @Override public boolean equals(Object other) { if (this == other) { return true; } ListenerCacheKey otherKey = (ListenerCacheKey) other; return (this.eventType.equals(otherKey.eventType) && ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType)); } @Override public int hashCode() { return this.eventType.hashCode() * 29 + ObjectUtils.nullSafeHashCode(this.sourceType); } @Override public String toString() { return "ListenerCacheKey [eventType = " + this.eventType + ", sourceType = " + this.sourceType + "]"; } @Override public int compareTo(ListenerCacheKey other) { int result = this.eventType.toString().compareTo(other.eventType.toString()); if (result == 0) { if (this.sourceType == null) { return (other.sourceType == null ? 0 : -1); } if (other.sourceType == null) { return 1; } result = this.sourceType.getName().compareTo(other.sourceType.getName()); } return result; } }
Helper class that encapsulates a specific set of target listeners, allowing for efficient retrieval of pre-filtered listeners.

An instance of this helper gets cached per event type and source type.

/** * Helper class that encapsulates a specific set of target listeners, * allowing for efficient retrieval of pre-filtered listeners. * <p>An instance of this helper gets cached per event type and source type. */
private class ListenerRetriever { public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>(); public final Set<String> applicationListenerBeans = new LinkedHashSet<>(); private final boolean preFiltered; public ListenerRetriever(boolean preFiltered) { this.preFiltered = preFiltered; } public Collection<ApplicationListener<?>> getApplicationListeners() { List<ApplicationListener<?>> allListeners = new ArrayList<>( this.applicationListeners.size() + this.applicationListenerBeans.size()); allListeners.addAll(this.applicationListeners); if (!this.applicationListenerBeans.isEmpty()) { BeanFactory beanFactory = getBeanFactory(); for (String listenerBeanName : this.applicationListenerBeans) { try { ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); if (this.preFiltered || !allListeners.contains(listener)) { allListeners.add(listener); } } catch (NoSuchBeanDefinitionException ex) { // Singleton listener instance (without backing bean definition) disappeared - // probably in the middle of the destruction phase } } } if (!this.preFiltered || !this.applicationListenerBeans.isEmpty()) { AnnotationAwareOrderComparator.sort(allListeners); } return allListeners; } } }