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

TAKETODAY / today-infrastructure / 20092525534

10 Dec 2025 08:42AM UTC coverage: 84.41% (+0.001%) from 84.409%
20092525534

push

github

TAKETODAY
:sparkles: 新增 getContentType() 方法用于解析请求的内容类型

- 优化 MediaType 参数解析逻辑,直接使用 ContentType 对象
- 移除对字符串类型 Content-Type 的直接依赖
- 更新相关注解和文档引用指向新的 ContentType 解析方法
- 添加对 multipart 请求类型的判断支持
- 完善 RequestContext 中的内容类型缓存机制
- 修复可能因空字符串导致的媒体类型解析异常

61723 of 78189 branches covered (78.94%)

Branch coverage included in aggregate %.

145744 of 167596 relevant lines covered (86.96%)

3.71 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 org.jspecify.annotations.Nullable;
21

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

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

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

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

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

71
  @Nullable
72
  private ClassLoader beanClassLoader;
73

74
  @Nullable
75
  private ConfigurableBeanFactory beanFactory;
76

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

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

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

101
  @Override
102
  public void addApplicationListener(ApplicationListener listener) {
103
    synchronized(this.listenerRetriever) {
5✔
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);
3✔
107
      if (singletonTarget instanceof ApplicationListener) {
3✔
108
        this.listenerRetriever.applicationListeners.remove(singletonTarget);
6✔
109
      }
110
      this.listenerRetriever.applicationListeners.add(listener);
6✔
111
      this.retrieverCache.clear();
3✔
112
    }
3✔
113
  }
1✔
114

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

432
    @Nullable
433
    public volatile Set<String> applicationListenerBeans;
434

435
    @Nullable
436
    public volatile Set<ApplicationListener<?>> applicationListeners;
437

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

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

469
  /**
470
   * Helper class that encapsulates a general set of target listeners.
471
   */
472
  private final class DefaultListenerRetriever {
6✔
473

474
    public final LinkedHashSet<String> applicationListenerBeans = new LinkedHashSet<>();
5✔
475

476
    public final LinkedHashSet<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
5✔
477

478
    public Collection<ApplicationListener<?>> getApplicationListeners() {
479
      ArrayList<ApplicationListener<?>> allListeners = new ArrayList<>(
4✔
480
              applicationListeners.size() + applicationListenerBeans.size());
7✔
481
      allListeners.addAll(applicationListeners);
5✔
482
      if (!applicationListenerBeans.isEmpty()) {
4✔
483
        BeanFactory beanFactory = getBeanFactory();
4✔
484
        for (String listenerBeanName : applicationListenerBeans) {
11✔
485
          try {
486
            ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
6✔
487
            if (!allListeners.contains(listener)) {
4!
488
              allListeners.add(listener);
×
489
            }
490
          }
491
          catch (NoSuchBeanDefinitionException ex) {
×
492
            // Singleton listener instance (without backing bean definition) disappeared -
493
            // probably in the middle of the destruction phase
494
          }
1✔
495
        }
1✔
496
      }
497
      AnnotationAwareOrderComparator.sort(allListeners);
2✔
498
      return allListeners;
2✔
499
    }
500
  }
501

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

© 2026 Coveralls, Inc