001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.context.event;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.LinkedHashSet;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025import java.util.concurrent.ConcurrentHashMap;
026
027import org.springframework.aop.framework.AopProxyUtils;
028import org.springframework.beans.factory.BeanClassLoaderAware;
029import org.springframework.beans.factory.BeanFactory;
030import org.springframework.beans.factory.BeanFactoryAware;
031import org.springframework.beans.factory.NoSuchBeanDefinitionException;
032import org.springframework.beans.factory.config.ConfigurableBeanFactory;
033import org.springframework.context.ApplicationEvent;
034import org.springframework.context.ApplicationListener;
035import org.springframework.core.ResolvableType;
036import org.springframework.core.annotation.AnnotationAwareOrderComparator;
037import org.springframework.util.ClassUtils;
038import org.springframework.util.ObjectUtils;
039
040/**
041 * Abstract implementation of the {@link ApplicationEventMulticaster} interface,
042 * providing the basic listener registration facility.
043 *
044 * <p>Doesn't permit multiple instances of the same listener by default,
045 * as it keeps listeners in a linked Set. The collection class used to hold
046 * ApplicationListener objects can be overridden through the "collectionClass"
047 * bean property.
048 *
049 * <p>Implementing ApplicationEventMulticaster's actual {@link #multicastEvent} method
050 * is left to subclasses. {@link SimpleApplicationEventMulticaster} simply multicasts
051 * all events to all registered listeners, invoking them in the calling thread.
052 * Alternative implementations could be more sophisticated in those respects.
053 *
054 * @author Juergen Hoeller
055 * @author Stephane Nicoll
056 * @since 1.2.3
057 * @see #getApplicationListeners(ApplicationEvent, ResolvableType)
058 * @see SimpleApplicationEventMulticaster
059 */
060public abstract class AbstractApplicationEventMulticaster
061                implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
062
063        private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);
064
065        final Map<ListenerCacheKey, ListenerRetriever> retrieverCache =
066                        new ConcurrentHashMap<ListenerCacheKey, ListenerRetriever>(64);
067
068        private ClassLoader beanClassLoader;
069
070        private BeanFactory beanFactory;
071
072        private Object retrievalMutex = this.defaultRetriever;
073
074
075        @Override
076        public void setBeanClassLoader(ClassLoader classLoader) {
077                this.beanClassLoader = classLoader;
078        }
079
080        @Override
081        public void setBeanFactory(BeanFactory beanFactory) {
082                this.beanFactory = beanFactory;
083                if (beanFactory instanceof ConfigurableBeanFactory) {
084                        ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
085                        if (this.beanClassLoader == null) {
086                                this.beanClassLoader = cbf.getBeanClassLoader();
087                        }
088                        this.retrievalMutex = cbf.getSingletonMutex();
089                }
090        }
091
092        private BeanFactory getBeanFactory() {
093                if (this.beanFactory == null) {
094                        throw new IllegalStateException("ApplicationEventMulticaster cannot retrieve listener beans " +
095                                        "because it is not associated with a BeanFactory");
096                }
097                return this.beanFactory;
098        }
099
100
101        @Override
102        public void addApplicationListener(ApplicationListener<?> listener) {
103                synchronized (this.retrievalMutex) {
104                        // Explicitly remove target for a proxy, if registered already,
105                        // in order to avoid double invocations of the same listener.
106                        Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
107                        if (singletonTarget instanceof ApplicationListener) {
108                                this.defaultRetriever.applicationListeners.remove(singletonTarget);
109                        }
110                        this.defaultRetriever.applicationListeners.add(listener);
111                        this.retrieverCache.clear();
112                }
113        }
114
115        @Override
116        public void addApplicationListenerBean(String listenerBeanName) {
117                synchronized (this.retrievalMutex) {
118                        this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);
119                        this.retrieverCache.clear();
120                }
121        }
122
123        @Override
124        public void removeApplicationListener(ApplicationListener<?> listener) {
125                synchronized (this.retrievalMutex) {
126                        this.defaultRetriever.applicationListeners.remove(listener);
127                        this.retrieverCache.clear();
128                }
129        }
130
131        @Override
132        public void removeApplicationListenerBean(String listenerBeanName) {
133                synchronized (this.retrievalMutex) {
134                        this.defaultRetriever.applicationListenerBeans.remove(listenerBeanName);
135                        this.retrieverCache.clear();
136                }
137        }
138
139        @Override
140        public void removeAllListeners() {
141                synchronized (this.retrievalMutex) {
142                        this.defaultRetriever.applicationListeners.clear();
143                        this.defaultRetriever.applicationListenerBeans.clear();
144                        this.retrieverCache.clear();
145                }
146        }
147
148
149        /**
150         * Return a Collection containing all ApplicationListeners.
151         * @return a Collection of ApplicationListeners
152         * @see org.springframework.context.ApplicationListener
153         */
154        protected Collection<ApplicationListener<?>> getApplicationListeners() {
155                synchronized (this.retrievalMutex) {
156                        return this.defaultRetriever.getApplicationListeners();
157                }
158        }
159
160        /**
161         * Return a Collection of ApplicationListeners matching the given
162         * event type. Non-matching listeners get excluded early.
163         * @param event the event to be propagated. Allows for excluding
164         * non-matching listeners early, based on cached matching information.
165         * @param eventType the event type
166         * @return a Collection of ApplicationListeners
167         * @see org.springframework.context.ApplicationListener
168         */
169        protected Collection<ApplicationListener<?>> getApplicationListeners(
170                        ApplicationEvent event, ResolvableType eventType) {
171
172                Object source = event.getSource();
173                Class<?> sourceType = (source != null ? source.getClass() : null);
174                ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
175
176                // Quick check for existing entry on ConcurrentHashMap...
177                ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
178                if (retriever != null) {
179                        return retriever.getApplicationListeners();
180                }
181
182                if (this.beanClassLoader == null ||
183                                (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
184                                                (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
185                        // Fully synchronized building and caching of a ListenerRetriever
186                        synchronized (this.retrievalMutex) {
187                                retriever = this.retrieverCache.get(cacheKey);
188                                if (retriever != null) {
189                                        return retriever.getApplicationListeners();
190                                }
191                                retriever = new ListenerRetriever(true);
192                                Collection<ApplicationListener<?>> listeners =
193                                                retrieveApplicationListeners(eventType, sourceType, retriever);
194                                this.retrieverCache.put(cacheKey, retriever);
195                                return listeners;
196                        }
197                }
198                else {
199                        // No ListenerRetriever caching -> no synchronization necessary
200                        return retrieveApplicationListeners(eventType, sourceType, null);
201                }
202        }
203
204        /**
205         * Actually retrieve the application listeners for the given event and source type.
206         * @param eventType the event type
207         * @param sourceType the event source type
208         * @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes)
209         * @return the pre-filtered list of application listeners for the given event and source type
210         */
211        private Collection<ApplicationListener<?>> retrieveApplicationListeners(
212                        ResolvableType eventType, Class<?> sourceType, ListenerRetriever retriever) {
213
214                List<ApplicationListener<?>> allListeners = new ArrayList<ApplicationListener<?>>();
215                Set<ApplicationListener<?>> listeners;
216                Set<String> listenerBeans;
217                synchronized (this.retrievalMutex) {
218                        listeners = new LinkedHashSet<ApplicationListener<?>>(this.defaultRetriever.applicationListeners);
219                        listenerBeans = new LinkedHashSet<String>(this.defaultRetriever.applicationListenerBeans);
220                }
221                for (ApplicationListener<?> listener : listeners) {
222                        if (supportsEvent(listener, eventType, sourceType)) {
223                                if (retriever != null) {
224                                        retriever.applicationListeners.add(listener);
225                                }
226                                allListeners.add(listener);
227                        }
228                }
229                if (!listenerBeans.isEmpty()) {
230                        BeanFactory beanFactory = getBeanFactory();
231                        for (String listenerBeanName : listenerBeans) {
232                                try {
233                                        Class<?> listenerType = beanFactory.getType(listenerBeanName);
234                                        if (listenerType == null || supportsEvent(listenerType, eventType)) {
235                                                ApplicationListener<?> listener =
236                                                                beanFactory.getBean(listenerBeanName, ApplicationListener.class);
237                                                if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
238                                                        if (retriever != null) {
239                                                                retriever.applicationListenerBeans.add(listenerBeanName);
240                                                        }
241                                                        allListeners.add(listener);
242                                                }
243                                        }
244                                }
245                                catch (NoSuchBeanDefinitionException ex) {
246                                        // Singleton listener instance (without backing bean definition) disappeared -
247                                        // probably in the middle of the destruction phase
248                                }
249                        }
250                }
251                AnnotationAwareOrderComparator.sort(allListeners);
252                return allListeners;
253        }
254
255        /**
256         * Filter a listener early through checking its generically declared event
257         * type before trying to instantiate it.
258         * <p>If this method returns {@code true} for a given listener as a first pass,
259         * the listener instance will get retrieved and fully evaluated through a
260         * {@link #supportsEvent(ApplicationListener, ResolvableType, Class)} call afterwards.
261         * @param listenerType the listener's type as determined by the BeanFactory
262         * @param eventType the event type to check
263         * @return whether the given listener should be included in the candidates
264         * for the given event type
265         */
266        protected boolean supportsEvent(Class<?> listenerType, ResolvableType eventType) {
267                if (GenericApplicationListener.class.isAssignableFrom(listenerType) ||
268                                SmartApplicationListener.class.isAssignableFrom(listenerType)) {
269                        return true;
270                }
271                ResolvableType declaredEventType = GenericApplicationListenerAdapter.resolveDeclaredEventType(listenerType);
272                return (declaredEventType == null || declaredEventType.isAssignableFrom(eventType));
273        }
274
275        /**
276         * Determine whether the given listener supports the given event.
277         * <p>The default implementation detects the {@link SmartApplicationListener}
278         * and {@link GenericApplicationListener} interfaces. In case of a standard
279         * {@link ApplicationListener}, a {@link GenericApplicationListenerAdapter}
280         * will be used to introspect the generically declared type of the target listener.
281         * @param listener the target listener to check
282         * @param eventType the event type to check against
283         * @param sourceType the source type to check against
284         * @return whether the given listener should be included in the candidates
285         * for the given event type
286         */
287        protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, Class<?> sourceType) {
288                GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
289                                (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
290                return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
291        }
292
293
294        /**
295         * Cache key for ListenerRetrievers, based on event type and source type.
296         */
297        private static final class ListenerCacheKey implements Comparable<ListenerCacheKey> {
298
299                private final ResolvableType eventType;
300
301                private final Class<?> sourceType;
302
303                public ListenerCacheKey(ResolvableType eventType, Class<?> sourceType) {
304                        this.eventType = eventType;
305                        this.sourceType = sourceType;
306                }
307
308                @Override
309                public boolean equals(Object other) {
310                        if (this == other) {
311                                return true;
312                        }
313                        ListenerCacheKey otherKey = (ListenerCacheKey) other;
314                        return (ObjectUtils.nullSafeEquals(this.eventType, otherKey.eventType) &&
315                                        ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType));
316                }
317
318                @Override
319                public int hashCode() {
320                        return (ObjectUtils.nullSafeHashCode(this.eventType) * 29 + ObjectUtils.nullSafeHashCode(this.sourceType));
321                }
322
323                @Override
324                public String toString() {
325                        return "ListenerCacheKey [eventType = " + this.eventType + ", sourceType = " + this.sourceType.getName() + "]";
326                }
327
328                @Override
329                public int compareTo(ListenerCacheKey other) {
330                        int result = 0;
331                        if (this.eventType != null) {
332                                result = this.eventType.toString().compareTo(other.eventType.toString());
333                        }
334                        if (result == 0 && this.sourceType != null) {
335                                result = this.sourceType.getName().compareTo(other.sourceType.getName());
336                        }
337                        return result;
338                }
339        }
340
341
342        /**
343         * Helper class that encapsulates a specific set of target listeners,
344         * allowing for efficient retrieval of pre-filtered listeners.
345         * <p>An instance of this helper gets cached per event type and source type.
346         */
347        private class ListenerRetriever {
348
349                public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<ApplicationListener<?>>();
350
351                public final Set<String> applicationListenerBeans = new LinkedHashSet<String>();
352
353                private final boolean preFiltered;
354
355                public ListenerRetriever(boolean preFiltered) {
356                        this.preFiltered = preFiltered;
357                }
358
359                public Collection<ApplicationListener<?>> getApplicationListeners() {
360                        List<ApplicationListener<?>> allListeners = new ArrayList<ApplicationListener<?>>(
361                                        this.applicationListeners.size() + this.applicationListenerBeans.size());
362                        allListeners.addAll(this.applicationListeners);
363                        if (!this.applicationListenerBeans.isEmpty()) {
364                                BeanFactory beanFactory = getBeanFactory();
365                                for (String listenerBeanName : this.applicationListenerBeans) {
366                                        try {
367                                                ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
368                                                if (this.preFiltered || !allListeners.contains(listener)) {
369                                                        allListeners.add(listener);
370                                                }
371                                        }
372                                        catch (NoSuchBeanDefinitionException ex) {
373                                                // Singleton listener instance (without backing bean definition) disappeared -
374                                                // probably in the middle of the destruction phase
375                                        }
376                                }
377                        }
378                        AnnotationAwareOrderComparator.sort(allListeners);
379                        return allListeners;
380                }
381        }
382
383}