• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 16523034142

25 Jul 2025 01:22PM UTC coverage: 81.778% (-0.004%) from 81.782%
16523034142

push

github

TAKETODAY
:art: Cache API

59440 of 77637 branches covered (76.56%)

Branch coverage included in aggregate %.

140761 of 167174 relevant lines covered (84.2%)

3.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.02
today-context/src/main/java/infra/context/event/AbstractApplicationEventMulticaster.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.context.event;
19

20
import java.util.ArrayList;
21
import java.util.Collection;
22
import java.util.LinkedHashSet;
23
import java.util.Set;
24
import java.util.concurrent.ConcurrentHashMap;
25
import java.util.function.Predicate;
26

27
import infra.aop.framework.AopProxyUtils;
28
import infra.beans.factory.BeanClassLoaderAware;
29
import infra.beans.factory.BeanFactory;
30
import infra.beans.factory.BeanFactoryAware;
31
import infra.beans.factory.NoSuchBeanDefinitionException;
32
import infra.beans.factory.config.BeanDefinition;
33
import infra.beans.factory.config.ConfigurableBeanFactory;
34
import infra.context.ApplicationEvent;
35
import infra.context.ApplicationListener;
36
import infra.core.ResolvableType;
37
import infra.core.annotation.AnnotationAwareOrderComparator;
38
import infra.lang.Assert;
39
import infra.lang.Nullable;
40
import infra.util.ClassUtils;
41

42
/**
43
 * Abstract implementation of the {@link ApplicationEventMulticaster} interface,
44
 * providing the basic listener registration facility.
45
 *
46
 * <p>Doesn't permit multiple instances of the same listener by default,
47
 * as it keeps listeners in a linked Set. The collection class used to hold
48
 * ApplicationListener objects can be overridden through the "collectionClass"
49
 * bean property.
50
 *
51
 * <p>Implementing ApplicationEventMulticaster's actual {@link #multicastEvent} method
52
 * is left to subclasses. {@link SimpleApplicationEventMulticaster} simply multicasts
53
 * all events to all registered listeners, invoking them in the calling thread.
54
 * Alternative implementations could be more sophisticated in those respects.
55
 *
56
 * @author Juergen Hoeller
57
 * @author Stephane Nicoll
58
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
59
 * @see #getApplicationListeners(ApplicationEvent, ResolvableType)
60
 * @see SimpleApplicationEventMulticaster
61
 * @since 4.0
62
 */
63
@SuppressWarnings({ "rawtypes" })
64
public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
2✔
65

66
  private final DefaultListenerRetriever listenerRetriever = new DefaultListenerRetriever();
6✔
67

68
  final ConcurrentHashMap<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
7✔
69

70
  @Nullable
71
  private ClassLoader beanClassLoader;
72

73
  @Nullable
74
  private ConfigurableBeanFactory beanFactory;
75

76
  @Override
77
  public void setBeanClassLoader(@Nullable ClassLoader classLoader) {
78
    this.beanClassLoader = classLoader;
3✔
79
  }
1✔
80

81
  @Override
82
  public void setBeanFactory(BeanFactory beanFactory) {
83
    if (!(beanFactory instanceof ConfigurableBeanFactory)) {
3!
84
      throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
×
85
    }
86
    this.beanFactory = (ConfigurableBeanFactory) beanFactory;
4✔
87
    if (this.beanClassLoader == null) {
3✔
88
      this.beanClassLoader = this.beanFactory.getBeanClassLoader();
5✔
89
    }
90
  }
1✔
91

92
  private ConfigurableBeanFactory getBeanFactory() {
93
    if (this.beanFactory == null) {
3!
94
      throw new IllegalStateException("ApplicationEventMulticaster cannot retrieve listener beans " +
×
95
              "because it is not associated with a BeanFactory");
96
    }
97
    return this.beanFactory;
3✔
98
  }
99

100
  @Override
101
  public void addApplicationListener(ApplicationListener listener) {
102
    synchronized(this.listenerRetriever) {
5✔
103
      // Explicitly remove target for a proxy, if registered already,
104
      // in order to avoid double invocations of the same listener.
105
      Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
3✔
106
      if (singletonTarget instanceof ApplicationListener) {
3✔
107
        this.listenerRetriever.applicationListeners.remove(singletonTarget);
6✔
108
      }
109
      this.listenerRetriever.applicationListeners.add(listener);
6✔
110
      this.retrieverCache.clear();
3✔
111
    }
3✔
112
  }
1✔
113

114
  @Override
115
  public void addApplicationListenerBean(String listenerBeanName) {
116
    synchronized(this.listenerRetriever) {
5✔
117
      this.listenerRetriever.applicationListenerBeans.add(listenerBeanName);
6✔
118
      this.retrieverCache.clear();
3✔
119
    }
3✔
120
  }
1✔
121

122
  @Override
123
  public void removeApplicationListener(ApplicationListener<?> listener) {
124
    synchronized(this.listenerRetriever) {
5✔
125
      this.listenerRetriever.applicationListeners.remove(listener);
6✔
126
      this.retrieverCache.clear();
3✔
127
    }
3✔
128
  }
1✔
129

130
  @Override
131
  public void removeApplicationListenerBean(String listenerBeanName) {
132
    synchronized(this.listenerRetriever) {
5✔
133
      this.listenerRetriever.applicationListenerBeans.remove(listenerBeanName);
6✔
134
      this.retrieverCache.clear();
3✔
135
    }
3✔
136
  }
1✔
137

138
  @Override
139
  public void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate) {
140
    synchronized(this.listenerRetriever) {
5✔
141
      this.listenerRetriever.applicationListeners.removeIf(predicate);
6✔
142
      this.retrieverCache.clear();
3✔
143
    }
3✔
144
  }
1✔
145

146
  @Override
147
  public void removeApplicationListenerBeans(Predicate<String> predicate) {
148
    synchronized(this.listenerRetriever) {
×
149
      this.listenerRetriever.applicationListenerBeans.removeIf(predicate);
×
150
      this.retrieverCache.clear();
×
151
    }
×
152
  }
×
153

154
  @Override
155
  public void removeAllListeners() {
156
    synchronized(this.listenerRetriever) {
×
157
      this.listenerRetriever.applicationListeners.clear();
×
158
      this.listenerRetriever.applicationListenerBeans.clear();
×
159
      this.retrieverCache.clear();
×
160
    }
×
161
  }
×
162

163
  /**
164
   * Return a Collection containing all ApplicationListeners.
165
   *
166
   * @return a Collection of ApplicationListeners
167
   * @see ApplicationListener
168
   */
169
  protected Collection<ApplicationListener<?>> getApplicationListeners() {
170
    synchronized(this.listenerRetriever) {
5✔
171
      return this.listenerRetriever.getApplicationListeners();
6✔
172
    }
173
  }
174

175
  /**
176
   * Return a Collection of ApplicationListeners matching the given
177
   * event type. Non-matching listeners get excluded early.
178
   *
179
   * @param event the event to be propagated. Allows for excluding
180
   * non-matching listeners early, based on cached matching information.
181
   * @param eventType the event type
182
   * @return a Collection of ApplicationListeners
183
   * @see ApplicationListener
184
   */
185
  protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
186
    Object source = event.getSource();
3✔
187
    Class<?> sourceType = (source != null ? source.getClass() : null);
7✔
188
    ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
6✔
189

190
    // Potential new retriever to populate
191
    CachedListenerRetriever newRetriever = null;
2✔
192

193
    // Quick check for existing entry on ConcurrentHashMap
194
    CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);
6✔
195
    if (existingRetriever == null) {
2✔
196
      // Caching a new ListenerRetriever if possible
197
      if (this.beanClassLoader == null ||
4✔
198
              (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
10!
199
                      (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
2!
200
        newRetriever = new CachedListenerRetriever();
5✔
201
        existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
7✔
202
        if (existingRetriever != null) {
2!
203
          newRetriever = null;  // no need to populate it in retrieveApplicationListeners
×
204
        }
205
      }
206
    }
207

208
    if (existingRetriever != null) {
2✔
209
      Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
3✔
210
      if (result != null) {
2✔
211
        return result;
2✔
212
      }
213
      // If result is null, the existing retriever is not fully populated yet by another thread.
214
      // Proceed like caching wasn't possible for this current local attempt.
215
    }
216

217
    return retrieveApplicationListeners(eventType, sourceType, newRetriever);
6✔
218
  }
219

220
  /**
221
   * Actually retrieve the application listeners for the given event and source type.
222
   *
223
   * @param eventType the event type
224
   * @param sourceType the event source type
225
   * @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes)
226
   * @return the pre-filtered list of application listeners for the given event and source type
227
   */
228
  private Collection<ApplicationListener<?>> retrieveApplicationListeners(
229
          ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {
230

231
    ArrayList<ApplicationListener<?>> allListeners = new ArrayList<>();
4✔
232
    LinkedHashSet<ApplicationListener<?>> filteredListeners = retriever != null ? new LinkedHashSet<>() : null;
8✔
233
    LinkedHashSet<String> filteredListenerBeans = retriever != null ? new LinkedHashSet<>() : null;
8✔
234

235
    Set<ApplicationListener<?>> listeners;
236
    Set<String> listenerBeans;
237
    synchronized(this.listenerRetriever) {
5✔
238
      listeners = new LinkedHashSet<>(this.listenerRetriever.applicationListeners);
7✔
239
      listenerBeans = new LinkedHashSet<>(this.listenerRetriever.applicationListenerBeans);
7✔
240
    }
3✔
241

242
    // Add programmatically registered listeners, including ones coming
243
    // from ApplicationListenerDetector (singleton beans and inner beans).
244
    for (ApplicationListener listener : listeners) {
10✔
245
      if (supportsEvent(listener, eventType, sourceType)) {
6✔
246
        if (retriever != null) {
2!
247
          filteredListeners.add(listener);
4✔
248
        }
249
        allListeners.add(listener);
4✔
250
      }
251
    }
1✔
252

253
    // Add listeners by bean name, potentially overlapping with programmatically
254
    // registered listeners above - but here potentially with additional metadata.
255
    if (!listenerBeans.isEmpty()) {
3✔
256
      ConfigurableBeanFactory beanFactory = getBeanFactory();
3✔
257
      for (String listenerBeanName : listenerBeans) {
10✔
258
        try {
259
          if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
6✔
260
            ApplicationListener listener =
3✔
261
                    beanFactory.getBean(listenerBeanName, ApplicationListener.class);
3✔
262

263
            // Despite best efforts to avoid it, unwrapped proxies (singleton targets) can end up in the
264
            // list of programmatically registered listeners. In order to avoid duplicates, we need to find
265
            // and replace them by their proxy counterparts, because if both a proxy and its target end up
266
            // in 'allListeners', listeners will fire twice.
267
            ApplicationListener<?> unwrappedListener =
1✔
268
                    (ApplicationListener<?>) AopProxyUtils.getSingletonTarget(listener);
3✔
269
            if (listener != unwrappedListener) {
3!
270
              if (filteredListeners != null && filteredListeners.contains(unwrappedListener)) {
6!
271
                filteredListeners.remove(unwrappedListener);
×
272
                filteredListeners.add(listener);
×
273
              }
274
              if (allListeners.contains(unwrappedListener)) {
4!
275
                allListeners.remove(unwrappedListener);
×
276
                allListeners.add(listener);
×
277
              }
278
            }
279

280
            if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
10✔
281
              if (retriever != null) {
2✔
282
                if (beanFactory.isSingleton(listenerBeanName)) {
4✔
283
                  filteredListeners.add(listener);
5✔
284
                }
285
                else {
286
                  filteredListenerBeans.add(listenerBeanName);
4✔
287
                }
288
              }
289
              allListeners.add(listener);
4✔
290
            }
291
          }
1✔
292
          else {
293
            // Remove non-matching listeners that originally came from
294
            // ApplicationListenerDetector, possibly ruled out by additional
295
            // BeanDefinition metadata (e.g. factory method generics) above.
296
            Object listener = beanFactory.getSingleton(listenerBeanName);
4✔
297
            if (retriever != null) {
2!
298
              filteredListeners.remove(listener);
4✔
299
            }
300
            allListeners.remove(listener);
4✔
301
          }
302
        }
303
        catch (NoSuchBeanDefinitionException ex) {
×
304
          // Singleton listener instance (without backing bean definition) disappeared -
305
          // probably in the middle of the destruction phase
306
        }
1✔
307
      }
1✔
308
    }
309

310
    AnnotationAwareOrderComparator.sort(allListeners);
2✔
311
    if (retriever != null) {
2✔
312
      if (filteredListenerBeans.isEmpty()) {
3✔
313
        retriever.applicationListeners = new LinkedHashSet<>(allListeners);
7✔
314
      }
315
      else {
316
        retriever.applicationListeners = filteredListeners;
3✔
317
      }
318
      retriever.applicationListenerBeans = filteredListenerBeans;
3✔
319
    }
320
    return allListeners;
2✔
321
  }
322

323
  /**
324
   * Filter a bean-defined listener early through checking its generically declared
325
   * event type before trying to instantiate it.
326
   * <p>If this method returns {@code true} for a given listener as a first pass,
327
   * the listener instance will get retrieved and fully evaluated through a
328
   * {@link #supportsEvent(ApplicationListener, ResolvableType, Class)} call afterwards.
329
   *
330
   * @param beanFactory the BeanFactory that contains the listener beans
331
   * @param listenerBeanName the name of the bean in the BeanFactory
332
   * @param eventType the event type to check
333
   * @return whether the given listener should be included in the candidates
334
   * for the given event type
335
   * @see #supportsEvent(Class, ResolvableType)
336
   * @see #supportsEvent(ApplicationListener, ResolvableType, Class)
337
   */
338
  private boolean supportsEvent(ConfigurableBeanFactory beanFactory, String listenerBeanName, ResolvableType eventType) {
339
    Class<?> listenerType = beanFactory.getType(listenerBeanName);
4✔
340
    if (listenerType == null
4!
341
            || GenericApplicationListener.class.isAssignableFrom(listenerType)
4✔
342
            || SmartApplicationListener.class.isAssignableFrom(listenerType)) {
2!
343
      return true;
2✔
344
    }
345
    if (!supportsEvent(listenerType, eventType)) {
5✔
346
      return false;
2✔
347
    }
348

349
    try {
350
      BeanDefinition bd = beanFactory.getMergedBeanDefinition(listenerBeanName);
4✔
351
      ResolvableType genericEventType = bd.getResolvableType().as(ApplicationListener.class).getGeneric();
8✔
352
      return genericEventType == ResolvableType.NONE || genericEventType.isAssignableFrom(eventType);
11✔
353
    }
354
    catch (NoSuchBeanDefinitionException ex) {
×
355
      // Ignore - no need to check resolvable type for manually registered singleton
356
      return true;
×
357
    }
358
  }
359

360
  /**
361
   * Filter a listener early through checking its generically declared event
362
   * type before trying to instantiate it.
363
   * <p>If this method returns {@code true} for a given listener as a first pass,
364
   * the listener instance will get retrieved and fully evaluated through a
365
   * {@link #supportsEvent(ApplicationListener, ResolvableType, Class)} call afterwards.
366
   *
367
   * @param listenerType the listener's type as determined by the BeanFactory
368
   * @param eventType the event type to check
369
   * @return whether the given listener should be included in the candidates
370
   * for the given event type
371
   */
372
  protected boolean supportsEvent(Class<?> listenerType, ResolvableType eventType) {
373
    ResolvableType declaredEventType = GenericApplicationListenerAdapter.resolveDeclaredEventType(listenerType);
3✔
374
    return declaredEventType == null || declaredEventType.isAssignableFrom(eventType);
10!
375
  }
376

377
  /**
378
   * Determine whether the given listener supports the given event.
379
   * <p>The default implementation detects the {@link SmartApplicationListener}
380
   * and {@link GenericApplicationListener} interfaces. In case of a standard
381
   * {@link ApplicationListener}, a {@link GenericApplicationListenerAdapter}
382
   * will be used to introspect the generically declared type of the target listener.
383
   *
384
   * @param listener the target listener to check
385
   * @param eventType the event type to check against
386
   * @param sourceType the source type to check against
387
   * @return whether the given listener should be included in the candidates
388
   * for the given event type
389
   */
390
  protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
391
    GenericApplicationListener smartListener
392
            = listener instanceof GenericApplicationListener
3✔
393
            ? (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener);
8✔
394
    return smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType);
12!
395
  }
396

397
  /**
398
   * Cache key for ListenerRetrievers, based on event type and source type.
399
   */
400
  private record ListenerCacheKey(ResolvableType eventType, @Nullable Class<?> sourceType)
401
          implements Comparable<ListenerCacheKey> {
402

403
    private ListenerCacheKey {
8✔
404
      Assert.notNull(eventType, "Event type is required");
3✔
405
    }
1✔
406

407
    @Override
408
    public int compareTo(ListenerCacheKey other) {
409
      int result = eventType.toString().compareTo(other.eventType.toString());
×
410
      if (result == 0) {
×
411
        if (sourceType == null) {
×
412
          return (other.sourceType == null ? 0 : -1);
×
413
        }
414
        if (other.sourceType == null) {
×
415
          return 1;
×
416
        }
417
        result = sourceType.getName().compareTo(other.sourceType.getName());
×
418
      }
419
      return result;
×
420
    }
421
  }
422

423
  /**
424
   * Helper class that encapsulates a specific set of target listeners,
425
   * allowing for efficient retrieval of pre-filtered listeners.
426
   * <p>An instance of this helper gets cached per event type and source type.
427
   */
428
  private final class CachedListenerRetriever {
6✔
429

430
    @Nullable
431
    public volatile Set<String> applicationListenerBeans;
432

433
    @Nullable
434
    public volatile Set<ApplicationListener<?>> applicationListeners;
435

436
    @Nullable
437
    public Collection<ApplicationListener<?>> getApplicationListeners() {
438
      Set<ApplicationListener<?>> applicationListeners = this.applicationListeners;
3✔
439
      Set<String> applicationListenerBeans = this.applicationListenerBeans;
3✔
440
      if (applicationListeners == null || applicationListenerBeans == null) {
4!
441
        // Not fully populated yet
442
        return null;
2✔
443
      }
444

445
      ArrayList<ApplicationListener<?>> allListeners = new ArrayList<>(
3✔
446
              applicationListeners.size() + applicationListenerBeans.size());
6✔
447
      allListeners.addAll(applicationListeners);
4✔
448
      if (!applicationListenerBeans.isEmpty()) {
3✔
449
        BeanFactory beanFactory = getBeanFactory();
4✔
450
        for (String listenerBeanName : applicationListenerBeans) {
10✔
451
          try {
452
            allListeners.add(beanFactory.getBean(listenerBeanName, ApplicationListener.class));
8✔
453
          }
454
          catch (NoSuchBeanDefinitionException ex) {
×
455
            // Singleton listener instance (without backing bean definition) disappeared -
456
            // probably in the middle of the destruction phase
457
          }
1✔
458
        }
1✔
459
      }
460
      if (!applicationListenerBeans.isEmpty()) {
3✔
461
        AnnotationAwareOrderComparator.sort(allListeners);
2✔
462
      }
463
      return allListeners;
2✔
464
    }
465
  }
466

467
  /**
468
   * Helper class that encapsulates a general set of target listeners.
469
   */
470
  private final class DefaultListenerRetriever {
5✔
471

472
    public final LinkedHashSet<String> applicationListenerBeans = new LinkedHashSet<>();
5✔
473

474
    public final LinkedHashSet<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
6✔
475

476
    public Collection<ApplicationListener<?>> getApplicationListeners() {
477
      ArrayList<ApplicationListener<?>> allListeners = new ArrayList<>(
4✔
478
              applicationListeners.size() + applicationListenerBeans.size());
7✔
479
      allListeners.addAll(applicationListeners);
5✔
480
      if (!applicationListenerBeans.isEmpty()) {
4✔
481
        BeanFactory beanFactory = getBeanFactory();
4✔
482
        for (String listenerBeanName : applicationListenerBeans) {
11✔
483
          try {
484
            ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
6✔
485
            if (!allListeners.contains(listener)) {
4!
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
          }
1✔
493
        }
1✔
494
      }
495
      AnnotationAwareOrderComparator.sort(allListeners);
2✔
496
      return allListeners;
2✔
497
    }
498
  }
499

500
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc