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

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

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

90.0
today-context/src/main/java/infra/context/event/SimpleApplicationEventMulticaster.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.concurrent.Executor;
23
import java.util.concurrent.RejectedExecutionException;
24

25
import infra.beans.factory.BeanFactory;
26
import infra.context.ApplicationEvent;
27
import infra.context.ApplicationListener;
28
import infra.context.PayloadApplicationEvent;
29
import infra.core.ResolvableType;
30
import infra.core.task.SimpleAsyncTaskExecutor;
31
import infra.core.task.SyncTaskExecutor;
32
import infra.core.task.TaskExecutor;
33
import infra.logging.Logger;
34
import infra.logging.LoggerFactory;
35
import infra.scheduling.support.TaskUtils;
36
import infra.util.ErrorHandler;
37

38
/**
39
 * Simple implementation of the {@link ApplicationEventMulticaster} interface.
40
 *
41
 * <p>Multicasts all events to all registered listeners, leaving it up to
42
 * the listeners to ignore events that they are not interested in.
43
 * Listeners will usually perform corresponding {@code instanceof}
44
 * checks on the passed-in event object.
45
 *
46
 * <p>By default, all listeners are invoked in the calling thread.
47
 * This allows the danger of a rogue listener blocking the entire application,
48
 * but adds minimal overhead. Specify an alternative task executor to have
49
 * listeners executed in different threads, for example from a thread pool.
50
 *
51
 * @author Rod Johnson
52
 * @author Juergen Hoeller
53
 * @author Stephane Nicoll
54
 * @author Brian Clozel
55
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
56
 * @see #setTaskExecutor
57
 * @since 4.0
58
 */
59
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
60

61
  /**
62
   * The current task executor for this multicaster.
63
   */
64
  @Nullable
65
  protected Executor taskExecutor;
66

67
  /**
68
   * The current error handler for this multicaster.
69
   */
70
  @Nullable
71
  protected ErrorHandler errorHandler;
72

73
  @Nullable
74
  private volatile Logger lazyLogger;
75

76
  /**
77
   * Create a new SimpleApplicationEventMulticaster.
78
   */
79
  public SimpleApplicationEventMulticaster() {
2✔
80

81
  }
1✔
82

83
  /**
84
   * Create a new SimpleApplicationEventMulticaster for the given BeanFactory.
85
   */
86
  public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
2✔
87
    setBeanFactory(beanFactory);
3✔
88
  }
1✔
89

90
  /**
91
   * Set a custom executor (typically a {@link TaskExecutor})
92
   * to invoke each listener with.
93
   * <p>Default is equivalent to {@link SyncTaskExecutor},
94
   * executing all listeners synchronously in the calling thread.
95
   * <p>Consider specifying an asynchronous task executor here to not block the caller
96
   * until all listeners have been executed. However, note that asynchronous execution
97
   * will not participate in the caller's thread context (class loader, transaction context)
98
   * unless the TaskExecutor explicitly supports this.
99
   * <p>{@link ApplicationListener} instances which declare no support for asynchronous
100
   * execution ({@link ApplicationListener#supportsAsyncExecution()} always run within
101
   * the original thread which published the event, e.g. the transaction-synchronized
102
   * {@link infra.transaction.event.TransactionalApplicationListener}.
103
   *
104
   * @see SyncTaskExecutor
105
   * @see SimpleAsyncTaskExecutor
106
   */
107
  public void setTaskExecutor(@Nullable Executor taskExecutor) {
108
    this.taskExecutor = taskExecutor;
3✔
109
  }
1✔
110

111
  /**
112
   * Set the {@link ErrorHandler} to invoke in case an exception is thrown
113
   * from a listener.
114
   * <p>Default is none, with a listener exception stopping the current
115
   * multicast and getting propagated to the publisher of the current event.
116
   * If a {@linkplain #setTaskExecutor task executor} is specified, each
117
   * individual listener exception will get propagated to the executor but
118
   * won't necessarily stop execution of other listeners.
119
   * <p>Consider setting an {@link ErrorHandler} implementation that catches
120
   * and logs exceptions (
121
   * {@link TaskUtils#LOG_AND_SUPPRESS_ERROR_HANDLER})
122
   * or an implementation that logs exceptions while nevertheless propagating them
123
   * (e.g. {@link TaskUtils#LOG_AND_PROPAGATE_ERROR_HANDLER}).
124
   */
125
  public void setErrorHandler(@Nullable ErrorHandler errorHandler) {
126
    this.errorHandler = errorHandler;
3✔
127
  }
1✔
128

129
  @Override
130
  public void multicastEvent(ApplicationEvent event) {
131
    multicastEvent(event, null);
4✔
132
  }
1✔
133

134
  @Override
135
  public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
136
    if (eventType == null) {
2✔
137
      eventType = ResolvableType.forInstance(event);
3✔
138
    }
139
    Executor executor = taskExecutor;
3✔
140
    if (executor != null) {
2✔
141
      for (ApplicationListener<?> listener : getApplicationListeners(event, eventType)) {
13✔
142
        if (listener.supportsAsyncExecution()) {
3✔
143
          try {
144
            executor.execute(() -> invokeListener(listener, event));
11✔
145
          }
146
          catch (RejectedExecutionException ex) {
×
147
            // Probably on shutdown -> invoke listener locally instead
148
            invokeListener(listener, event);
×
149
          }
1✔
150
        }
151
        else {
152
          invokeListener(listener, event);
4✔
153
        }
154
      }
2✔
155
    }
156
    else {
157
      for (ApplicationListener<?> listener : getApplicationListeners(event, eventType)) {
13✔
158
        invokeListener(listener, event);
4✔
159
      }
1✔
160
    }
161
  }
1✔
162

163
  /**
164
   * Invoke the given listener with the given event.
165
   *
166
   * @param listener the ApplicationListener to invoke
167
   * @param event the current event to propagate
168
   */
169
  protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
170
    ErrorHandler errorHandler = this.errorHandler;
3✔
171
    if (errorHandler != null) {
2✔
172
      try {
173
        doInvokeListener(listener, event);
4✔
174
      }
175
      catch (Throwable err) {
1✔
176
        errorHandler.handleError(err);
3✔
177
      }
2✔
178
    }
179
    else {
180
      doInvokeListener(listener, event);
4✔
181
    }
182
  }
1✔
183

184
  @SuppressWarnings({ "rawtypes", "unchecked" })
185
  private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
186
    try {
187
      listener.onApplicationEvent(event);
3✔
188
    }
189
    catch (ClassCastException ex) {
1✔
190
      String msg = ex.getMessage();
3✔
191
      if (msg == null || matchesClassCastMessage(msg, event.getClass())
11!
192
              || (
193
              event instanceof PayloadApplicationEvent pae
6✔
194
                      && matchesClassCastMessage(msg, pae.getPayload().getClass()))
4!
195
      ) {
196
        // Possibly a lambda-defined listener which we could not resolve the generic event type for
197
        // -> let's suppress the exception.
198
        Logger loggerToUse = this.lazyLogger;
3✔
199
        if (loggerToUse == null) {
2✔
200
          loggerToUse = LoggerFactory.getLogger(getClass());
4✔
201
          this.lazyLogger = loggerToUse;
3✔
202
        }
203
        if (loggerToUse.isTraceEnabled()) {
3!
204
          loggerToUse.trace("Non-matching event type for listener: {}", listener, ex);
×
205
        }
206
      }
1✔
207
      else {
208
        throw ex;
×
209
      }
210
    }
1✔
211
  }
1✔
212

213
  private boolean matchesClassCastMessage(String classCastMessage, Class<?> eventClass) {
214
    // On Java 8, the message starts with the class name: "java.lang.String cannot be cast..."
215
    if (classCastMessage.startsWith(eventClass.getName())) {
5✔
216
      return true;
2✔
217
    }
218
    // On Java 11, the message starts with "class ..." a.k.a. Class.toString()
219
    if (classCastMessage.startsWith(eventClass.toString())) {
5✔
220
      return true;
2✔
221
    }
222
    // On Java 9, the message used to contain the module name: "java.base/java.lang.String cannot be cast..."
223
    int moduleSeparatorIndex = classCastMessage.indexOf('/');
4✔
224
    // false Assuming an unrelated class cast failure...
225
    return moduleSeparatorIndex != -1
6✔
226
            && classCastMessage.startsWith(eventClass.getName(), moduleSeparatorIndex + 1);
9!
227
  }
228
}
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