001/*
002 * Copyright 2002-2020 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.BeanDefinition;
033import org.springframework.beans.factory.config.ConfigurableBeanFactory;
034import org.springframework.context.ApplicationEvent;
035import org.springframework.context.ApplicationListener;
036import org.springframework.core.ResolvableType;
037import org.springframework.core.annotation.AnnotationAwareOrderComparator;
038import org.springframework.lang.Nullable;
039import org.springframework.util.Assert;
040import org.springframework.util.ClassUtils;
041import org.springframework.util.ObjectUtils;
042
043/**
044 * Abstract implementation of the {@link ApplicationEventMulticaster} interface,
045 * providing the basic listener registration facility.
046 *
047 * <p>Doesn't permit multiple instances of the same listener by default,
048 * as it keeps listeners in a linked Set. The collection class used to hold
049 * ApplicationListener objects can be overridden through the "collectionClass"
050 * bean property.
051 *
052 * <p>Implementing ApplicationEventMulticaster's actual {@link #multicastEvent} method
053 * is left to subclasses. {@link SimpleApplicationEventMulticaster} simply multicasts
054 * all events to all registered listeners, invoking them in the calling thread.
055 * Alternative implementations could be more sophisticated in those respects.
056 *
057 * @author Juergen Hoeller
058 * @author Stephane Nicoll
059 * @since 1.2.3
060 * @see #getApplicationListeners(ApplicationEvent, ResolvableType)
061 * @see SimpleApplicationEventMulticaster
062 */
063public abstract class AbstractApplicationEventMulticaster
064                implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
065
066        private final DefaultListenerRetriever defaultRetriever = new DefaultListenerRetriever();
067
068        final Map<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
069
070        @Nullable
071        private ClassLoader beanClassLoader;
072
073        @Nullable
074        private ConfigurableBeanFactory beanFactory;
075
076
077        @Override
078        public void setBeanClassLoader(ClassLoader classLoader) {
079                this.beanClassLoader = classLoader;
080        }
081
082        @Override
083        public void setBeanFactory(BeanFactory beanFactory) {
084                if (!(beanFactory instanceof ConfigurableBeanFactory)) {
085                        throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
086                }
087                this.beanFactory = (ConfigurableBeanFactory) beanFactory;
088                if (this.beanClassLoader == null) {
089                        this.beanClassLoader = this.beanFactory.getBeanClassLoader();
090                }
091        }
092
093        private ConfigurableBeanFactory getBeanFactory() {
094                if (this.beanFactory == null) {
095                        throw new IllegalStateException("ApplicationEventMulticaster cannot retrieve listener beans " +
096                                        "because it is not associated with a BeanFactory");
097                }
098                return this.beanFactory;
099        }
100
101
102        @Override
103        public void addApplicationListener(ApplicationListener<?> listener) {
104                synchronized (this.defaultRetriever) {
105                        // Explicitly remove target for a proxy, if registered already,
106                        // in order to avoid double invocations of the same listener.
107                        Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
108                        if (singletonTarget instanceof ApplicationListener) {
109                                this.defaultRetriever.applicationListeners.remove(singletonTarget);
110                        }
111                        this.defaultRetriever.applicationListeners.add(listener);
112                        this.retrieverCache.clear();
113                }
114        }
115
116        @Override
117        public void addApplicationListenerBean(String listenerBeanName) {
118                synchronized (this.defaultRetriever) {
119                        this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);
120                        this.retrieverCache.clear();
121                }
122        }
123
124        @Override
125        public void removeApplicationListener(ApplicationListener<?> listener) {
126                synchronized (this.defaultRetriever) {
127                        this.defaultRetriever.applicationListeners.remove(listener);
128                        this.retrieverCache.clear();
129                }
130        }
131
132        @Override
133        public void removeApplicationListenerBean(String listenerBeanName) {
134                synchronized (this.defaultRetriever) {
135                        this.defaultRetriever.applicationListenerBeans.remove(listenerBeanName);
136                        this.retrieverCache.clear();
137                }
138        }
139
140        @Override
141        public void removeAllListeners() {
142                synchronized (this.defaultRetriever) {
143                        this.defaultRetriever.applicationListeners.clear();
144                        this.defaultRetriever.applicationListenerBeans.clear();
145                        this.retrieverCache.clear();
146                }
147        }
148
149
150        /**
151         * Return a Collection containing all ApplicationListeners.
152         * @return a Collection of ApplicationListeners
153         * @see org.springframework.context.ApplicationListener
154         */
155        protected Collection<ApplicationListener<?>> getApplicationListeners() {
156                synchronized (this.defaultRetriever) {
157                        return this.defaultRetriever.getApplicationListeners();
158                }
159        }
160
161        /**
162         * Return a Collection of ApplicationListeners matching the given
163         * event type. Non-matching listeners get excluded early.
164         * @param event the event to be propagated. Allows for excluding
165         * non-matching listeners early, based on cached matching information.
166         * @param eventType the event type
167         * @return a Collection of ApplicationListeners
168         * @see org.springframework.context.ApplicationListener
169         */
170        protected Collection<ApplicationListener<?>> getApplicationListeners(
171                        ApplicationEvent event, ResolvableType eventType) {
172
173                Object source = event.getSource();
174                Class<?> sourceType = (source != null ? source.getClass() : null);
175                ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
176
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
191                                }
192                        }
193                }
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.
202                }
203
204                return retrieveApplicationListeners(eventType, sourceType, newRetriever);
205        }
206
207        /**
208         * Actually retrieve the application listeners for the given event and source type.
209         * @param eventType the event type
210         * @param sourceType the event source type
211         * @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes)
212         * @return the pre-filtered list of application listeners for the given event and source type
213         */
214        private Collection<ApplicationListener<?>> retrieveApplicationListeners(
215                        ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {
216
217                List<ApplicationListener<?>> allListeners = new ArrayList<>();
218                Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);
219                Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);
220
221                Set<ApplicationListener<?>> listeners;
222                Set<String> listenerBeans;
223                synchronized (this.defaultRetriever) {
224                        listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
225                        listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
226                }
227
228                // Add programmatically registered listeners, including ones coming
229                // from ApplicationListenerDetector (singleton beans and inner beans).
230                for (ApplicationListener<?> listener : listeners) {
231                        if (supportsEvent(listener, eventType, sourceType)) {
232                                if (retriever != null) {
233                                        filteredListeners.add(listener);
234                                }
235                                allListeners.add(listener);
236                        }
237                }
238
239                // Add listeners by bean name, potentially overlapping with programmatically
240                // registered listeners above - but here potentially with additional metadata.
241                if (!listenerBeans.isEmpty()) {
242                        ConfigurableBeanFactory beanFactory = getBeanFactory();
243                        for (String listenerBeanName : listenerBeans) {
244                                try {
245                                        if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
246                                                ApplicationListener<?> listener =
247                                                                beanFactory.getBean(listenerBeanName, ApplicationListener.class);
248                                                if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
249                                                        if (retriever != null) {
250                                                                if (beanFactory.isSingleton(listenerBeanName)) {
251                                                                        filteredListeners.add(listener);
252                                                                }
253                                                                else {
254                                                                        filteredListenerBeans.add(listenerBeanName);
255                                                                }
256                                                        }
257                                                        allListeners.add(listener);
258                                                }
259                                        }
260                                        else {
261                                                // Remove non-matching listeners that originally came from
262                                                // ApplicationListenerDetector, possibly ruled out by additional
263                                                // BeanDefinition metadata (e.g. factory method generics) above.
264                                                Object listener = beanFactory.getSingleton(listenerBeanName);
265                                                if (retriever != null) {
266                                                        filteredListeners.remove(listener);
267                                                }
268                                                allListeners.remove(listener);
269                                        }
270                                }
271                                catch (NoSuchBeanDefinitionException ex) {
272                                        // Singleton listener instance (without backing bean definition) disappeared -
273                                        // probably in the middle of the destruction phase
274                                }
275                        }
276                }
277
278                AnnotationAwareOrderComparator.sort(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                        }
288                }
289                return allListeners;
290        }
291
292        /**
293         * Filter a bean-defined listener early through checking its generically declared
294         * event type before trying to instantiate it.
295         * <p>If this method returns {@code true} for a given listener as a first pass,
296         * the listener instance will get retrieved and fully evaluated through a
297         * {@link #supportsEvent(ApplicationListener, ResolvableType, Class)} call afterwards.
298         * @param beanFactory the BeanFactory that contains the listener beans
299         * @param listenerBeanName the name of the bean in the BeanFactory
300         * @param eventType the event type to check
301         * @return whether the given listener should be included in the candidates
302         * for the given event type
303         * @see #supportsEvent(Class, ResolvableType)
304         * @see #supportsEvent(ApplicationListener, ResolvableType, Class)
305         */
306        private boolean supportsEvent(
307                        ConfigurableBeanFactory beanFactory, String listenerBeanName, ResolvableType eventType) {
308
309                Class<?> listenerType = beanFactory.getType(listenerBeanName);
310                if (listenerType == null || GenericApplicationListener.class.isAssignableFrom(listenerType) ||
311                                SmartApplicationListener.class.isAssignableFrom(listenerType)) {
312                        return true;
313                }
314                if (!supportsEvent(listenerType, eventType)) {
315                        return false;
316                }
317                try {
318                        BeanDefinition bd = beanFactory.getMergedBeanDefinition(listenerBeanName);
319                        ResolvableType genericEventType = bd.getResolvableType().as(ApplicationListener.class).getGeneric();
320                        return (genericEventType == ResolvableType.NONE || genericEventType.isAssignableFrom(eventType));
321                }
322                catch (NoSuchBeanDefinitionException ex) {
323                        // Ignore - no need to check resolvable type for manually registered singleton
324                        return true;
325                }
326        }
327
328        /**
329         * Filter a listener early through checking its generically declared event
330         * type before trying to instantiate it.
331         * <p>If this method returns {@code true} for a given listener as a first pass,
332         * the listener instance will get retrieved and fully evaluated through a
333         * {@link #supportsEvent(ApplicationListener, ResolvableType, Class)} call afterwards.
334         * @param listenerType the listener's type as determined by the BeanFactory
335         * @param eventType the event type to check
336         * @return whether the given listener should be included in the candidates
337         * for the given event type
338         */
339        protected boolean supportsEvent(Class<?> listenerType, ResolvableType eventType) {
340                ResolvableType declaredEventType = GenericApplicationListenerAdapter.resolveDeclaredEventType(listenerType);
341                return (declaredEventType == null || declaredEventType.isAssignableFrom(eventType));
342        }
343
344        /**
345         * Determine whether the given listener supports the given event.
346         * <p>The default implementation detects the {@link SmartApplicationListener}
347         * and {@link GenericApplicationListener} interfaces. In case of a standard
348         * {@link ApplicationListener}, a {@link GenericApplicationListenerAdapter}
349         * will be used to introspect the generically declared type of the target listener.
350         * @param listener the target listener to check
351         * @param eventType the event type to check against
352         * @param sourceType the source type to check against
353         * @return whether the given listener should be included in the candidates
354         * for the given event type
355         */
356        protected boolean supportsEvent(
357                        ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
358
359                GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
360                                (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
361                return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
362        }
363
364
365        /**
366         * Cache key for ListenerRetrievers, based on event type and source type.
367         */
368        private static final class ListenerCacheKey implements Comparable<ListenerCacheKey> {
369
370                private final ResolvableType eventType;
371
372                @Nullable
373                private final Class<?> sourceType;
374
375                public ListenerCacheKey(ResolvableType eventType, @Nullable Class<?> sourceType) {
376                        Assert.notNull(eventType, "Event type must not be null");
377                        this.eventType = eventType;
378                        this.sourceType = sourceType;
379                }
380
381                @Override
382                public boolean equals(@Nullable Object other) {
383                        if (this == other) {
384                                return true;
385                        }
386                        if (!(other instanceof ListenerCacheKey)) {
387                                return false;
388                        }
389                        ListenerCacheKey otherKey = (ListenerCacheKey) other;
390                        return (this.eventType.equals(otherKey.eventType) &&
391                                        ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType));
392                }
393
394                @Override
395                public int hashCode() {
396                        return this.eventType.hashCode() * 29 + ObjectUtils.nullSafeHashCode(this.sourceType);
397                }
398
399                @Override
400                public String toString() {
401                        return "ListenerCacheKey [eventType = " + this.eventType + ", sourceType = " + this.sourceType + "]";
402                }
403
404                @Override
405                public int compareTo(ListenerCacheKey other) {
406                        int result = this.eventType.toString().compareTo(other.eventType.toString());
407                        if (result == 0) {
408                                if (this.sourceType == null) {
409                                        return (other.sourceType == null ? 0 : -1);
410                                }
411                                if (other.sourceType == null) {
412                                        return 1;
413                                }
414                                result = this.sourceType.getName().compareTo(other.sourceType.getName());
415                        }
416                        return result;
417                }
418        }
419
420
421        /**
422         * Helper class that encapsulates a specific set of target listeners,
423         * allowing for efficient retrieval of pre-filtered listeners.
424         * <p>An instance of this helper gets cached per event type and source type.
425         */
426        private class CachedListenerRetriever {
427
428                @Nullable
429                public volatile Set<ApplicationListener<?>> applicationListeners;
430
431                @Nullable
432                public volatile Set<String> applicationListenerBeans;
433
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                        }
442
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;
462                }
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<>();
474
475                public Collection<ApplicationListener<?>> getApplicationListeners() {
476                        List<ApplicationListener<?>> allListeners = new ArrayList<>(
477                                        this.applicationListeners.size() + this.applicationListenerBeans.size());
478                        allListeners.addAll(this.applicationListeners);
479                        if (!this.applicationListenerBeans.isEmpty()) {
480                                BeanFactory beanFactory = getBeanFactory();
481                                for (String listenerBeanName : this.applicationListenerBeans) {
482                                        try {
483                                                ApplicationListener<?> listener =
484                                                                beanFactory.getBean(listenerBeanName, ApplicationListener.class);
485                                                if (!allListeners.contains(listener)) {
486                                                        allListeners.add(listener);
487                                                }
488                                        }
489                                        catch (NoSuchBeanDefinitionException ex) {
490                                                // Singleton listener instance (without backing bean definition) disappeared -
491                                                // probably in the middle of the destruction phase
492                                        }
493                                }
494                        }
495                        AnnotationAwareOrderComparator.sort(allListeners);
496                        return allListeners;
497                }
498        }
499
500}