2525import java .util .function .Consumer ;
2626import java .util .function .Predicate ;
2727import java .util .function .Supplier ;
28+ import java .util .stream .Collectors ;
2829import java .util .stream .Stream ;
2930
3031import org .jspecify .annotations .Nullable ;
@@ -76,7 +77,7 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv
7677 static final String REPUBLISH_ON_RESTART = "spring.modulith.events.republish-outstanding-events-on-restart" ;
7778 static final String REPUBLISH_ON_RESTART_LEGACY = "spring.modulith.republish-outstanding-events-on-restart" ;
7879
79- private final Map <ResolvableType , TransactionalEventListeners > listenerCache = new ConcurrentReferenceHashMap <>();
80+ private final Map <CacheKey , TransactionalEventListeners > cache = new ConcurrentReferenceHashMap <>();
8081 private final Supplier <EventPublicationRegistry > registry ;
8182 private final Supplier <Environment > environment ;
8283
@@ -114,35 +115,34 @@ public void multicastEvent(ApplicationEvent event) {
114115 public void multicastEvent (ApplicationEvent event , @ Nullable ResolvableType eventType ) {
115116
116117 var type = eventType == null ? ResolvableType .forInstance (event ) : eventType ;
117- var listeners = getApplicationListeners (event , type );
118+ var candidates = super . getApplicationListeners (event , type );
118119
119- if (listeners .isEmpty ()) {
120+ if (candidates .isEmpty ()) {
120121 return ;
121122 }
122123
123- listenerCache
124- .computeIfAbsent (type , __ -> new TransactionalEventListeners (listeners , environment ))
125- .ifPresent (it -> storePublications (it , getEventToPersist (event )));
124+ var eventToPersist = getEventToPersist (event );
126125
127- for (ApplicationListener listener : listeners ) {
128- listener .onApplicationEvent (event );
126+ // Find all listeners that will need to be invoked
127+ var matchingListeners = candidates .stream () //
128+ .filter (it -> matches (event , eventToPersist , it )) //
129+ .toList ();
130+
131+ if (matchingListeners .isEmpty ()) {
132+ return ;
129133 }
130- }
131134
132- /*
133- * (non-Javadoc)
134- * @see org.springframework.context.event.AbstractApplicationEventMulticaster#getApplicationListeners(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
135- */
136- @ Override
137- protected Collection <ApplicationListener <?>> getApplicationListeners (ApplicationEvent event ,
138- ResolvableType eventType ) {
135+ // From the candidates find the transactional ones and cache them by source and target type
136+ cache .computeIfAbsent (new CacheKey (type , getSourceType (event )),
137+ __ -> new TransactionalEventListeners (candidates , environment ))
139138
140- Object eventToPersist = getEventToPersist (event );
139+ // Make sure we honor the by-event instance evaluated conditions
140+ .filter (matchingListeners ::contains )
141+ .ifPresent (stream -> storePublications (stream , eventToPersist ));
141142
142- return super .getApplicationListeners (event , eventType )
143- .stream ()
144- .filter (it -> matches (event , eventToPersist , it ))
145- .toList ();
143+ for (ApplicationListener listener : matchingListeners ) {
144+ listener .onApplicationEvent (event );
145+ }
146146 }
147147
148148 /*
@@ -202,8 +202,7 @@ public void afterSingletonsInstantiated() {
202202
203203 private void invokeTargetListener (TargetEventPublication publication ) {
204204
205- var listeners = new TransactionalEventListeners (
206- getApplicationListeners (), environment );
205+ var listeners = new TransactionalEventListeners (getApplicationListeners (), environment );
207206
208207 listeners .stream () //
209208 .filter (it -> publication .isIdentifiedBy (PublicationTargetIdentifier .of (it .getListenerId ()))) //
@@ -251,6 +250,13 @@ private static Object getEventToPersist(ApplicationEvent event) {
251250 : event ;
252251 }
253252
253+ private static @ Nullable Class <?> getSourceType (ApplicationEvent event ) {
254+
255+ var source = event .getSource ();
256+
257+ return source != null ? source .getClass () : null ;
258+ }
259+
254260 private static boolean matches (ApplicationEvent event , Object payload , ApplicationListener <?> listener ) {
255261
256262 // Verify general listener matching by eagerly evaluating the condition
@@ -280,6 +286,13 @@ private static boolean invokeShouldHandle(ApplicationListener<?> candidate, Appl
280286 return true ;
281287 }
282288
289+ private record CacheKey (ResolvableType eventType , @ Nullable Class <?> sourceType ) {
290+
291+ private CacheKey {
292+ Assert .notNull (eventType , "Event type must not be null" );
293+ }
294+ }
295+
283296 /**
284297 * First-class collection to work with transactional event listeners, i.e. {@link ApplicationListener} instances that
285298 * implement {@link TransactionalApplicationListener}.
@@ -324,6 +337,10 @@ public TransactionalEventListeners(Collection<ApplicationListener<?>> listeners,
324337 .toList ();
325338 }
326339
340+ private TransactionalEventListeners (List <TransactionalApplicationListener <ApplicationEvent >> listeners ) {
341+ this .listeners = listeners ;
342+ }
343+
327344 /**
328345 * Invokes the given {@link Consumer} for all transactional event listeners.
329346 *
@@ -350,6 +367,13 @@ public void ifPresent(Consumer<Stream<TransactionalApplicationListener<Applicati
350367 }
351368 }
352369
370+ public TransactionalEventListeners filter (
371+ Predicate <? super TransactionalApplicationListener <ApplicationEvent >> filter ) {
372+
373+ return listeners .stream ().filter (filter )
374+ .collect (Collectors .collectingAndThen (Collectors .toUnmodifiableList (), TransactionalEventListeners ::new ));
375+ }
376+
353377 /**
354378 * Returns all transactional event listeners.
355379 *
0 commit comments