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}