Skip to content

Commit 2148328

Browse files
committed
GH-277 - Multicaster now honors listener condition.
We now reflectively invoke ApplicationListenerMethodAdapter.shouldHandle(…) when selecting event listeners to make sure that conditions defined in, for example, @TransactionalEventListener are considered before registering an event publication.
1 parent f6a45c0 commit 2148328

File tree

2 files changed

+73
-7
lines changed

2 files changed

+73
-7
lines changed

spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticaster.java

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.modulith.events.support;
1717

18+
import java.lang.reflect.Method;
1819
import java.time.Duration;
1920
import java.util.Collection;
2021
import java.util.List;
@@ -31,6 +32,7 @@
3132
import org.springframework.context.PayloadApplicationEvent;
3233
import org.springframework.context.event.AbstractApplicationEventMulticaster;
3334
import org.springframework.context.event.ApplicationEventMulticaster;
35+
import org.springframework.context.event.ApplicationListenerMethodAdapter;
3436
import org.springframework.core.ResolvableType;
3537
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
3638
import org.springframework.core.env.Environment;
@@ -46,6 +48,7 @@
4648
import org.springframework.transaction.event.TransactionalApplicationListener;
4749
import org.springframework.transaction.event.TransactionalEventListener;
4850
import org.springframework.util.Assert;
51+
import org.springframework.util.ReflectionUtils;
4952

5053
/**
5154
* An {@link ApplicationEventMulticaster} to register {@link EventPublication}s in an {@link EventPublicationRegistry}
@@ -62,11 +65,17 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv
6265
implements IncompleteEventPublications, SmartInitializingSingleton {
6366

6467
private static final Logger LOGGER = LoggerFactory.getLogger(PersistentApplicationEventMulticaster.class);
68+
private static final Method SUPPORTS_METHOD = ReflectionUtils.findMethod(ApplicationListenerMethodAdapter.class,
69+
"shouldHandle", ApplicationEvent.class, Object[].class);
6570
static final String REPUBLISH_ON_RESTART = "spring.modulith.republish-outstanding-events-on-restart";
6671

6772
private final @NonNull Supplier<EventPublicationRegistry> registry;
6873
private final @NonNull Supplier<Environment> environment;
6974

75+
static {
76+
ReflectionUtils.makeAccessible(SUPPORTS_METHOD);
77+
}
78+
7079
/**
7180
* Creates a new {@link PersistentApplicationEventMulticaster} for the given {@link EventPublicationRegistry}.
7281
*
@@ -127,14 +136,14 @@ protected Collection<ApplicationListener<?>> getApplicationListeners(Application
127136

128137
return super.getApplicationListeners(event, eventType)
129138
.stream()
130-
.filter(it -> matches(eventToPersist, it))
139+
.filter(it -> matches(event, eventToPersist, it))
131140
.toList();
132141
}
133142

134-
/*
135-
* (non-Javadoc)
136-
* @see org.springframework.modulith.events.IncompleteEventPublications#resubmitIncompletePublications(java.util.function.Predicate)
137-
*/
143+
/*
144+
* (non-Javadoc)
145+
* @see org.springframework.modulith.events.IncompleteEventPublications#resubmitIncompletePublications(java.util.function.Predicate)
146+
*/
138147
@Override
139148
public void resubmitIncompletePublications(Predicate<EventPublication> filter) {
140149
doResubmitUncompletedPublicationsOlderThan(null);
@@ -219,10 +228,22 @@ private static Object getEventToPersist(ApplicationEvent event) {
219228
: event;
220229
}
221230

222-
private static boolean matches(Object event, ApplicationListener<?> listener) {
231+
@SuppressWarnings("null")
232+
private static boolean matches(ApplicationEvent event, Object payload, ApplicationListener<?> listener) {
233+
234+
// Verify general listener matching by eagerly evaluating the condition
235+
if (ApplicationListenerMethodAdapter.class.isInstance(listener)) {
236+
237+
boolean result = (boolean) ReflectionUtils.invokeMethod(SUPPORTS_METHOD, listener, event,
238+
new Object[] { payload });
239+
240+
if (!result) {
241+
return false;
242+
}
243+
}
223244

224245
return ConditionalEventListener.class.isInstance(listener)
225-
? ConditionalEventListener.class.cast(listener).supports(event)
246+
? ConditionalEventListener.class.cast(listener).supports(payload)
226247
: true;
227248
}
228249

spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticasterUnitTests.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,25 @@
1515
*/
1616
package org.springframework.modulith.events.support;
1717

18+
import static org.assertj.core.api.Assertions.*;
1819
import static org.mockito.Mockito.*;
1920

21+
import lombok.AllArgsConstructor;
22+
2023
import java.util.Map;
2124

2225
import org.junit.jupiter.api.BeforeEach;
2326
import org.junit.jupiter.api.Test;
27+
import org.springframework.context.PayloadApplicationEvent;
28+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
29+
import org.springframework.context.event.ApplicationEventMulticaster;
30+
import org.springframework.context.event.EventListenerMethodProcessor;
31+
import org.springframework.core.ResolvableType;
2432
import org.springframework.core.env.MapPropertySource;
2533
import org.springframework.core.env.StandardEnvironment;
2634
import org.springframework.modulith.events.core.EventPublicationRegistry;
35+
import org.springframework.stereotype.Component;
36+
import org.springframework.transaction.event.TransactionalEventListener;
2737

2838
/**
2939
* Unit tests for {@link PersistentApplicationEventMulticaster}.
@@ -61,4 +71,39 @@ void triggersRepublicationIfExplicitlyEnabled() {
6171

6272
verify(registry).findIncompletePublications();
6373
}
74+
75+
@Test // GH-277
76+
void honorsListenerCondition() throws Exception {
77+
78+
try (var ctx = new AnnotationConfigApplicationContext()) {
79+
80+
ctx.addBeanFactoryPostProcessor(new EventListenerMethodProcessor());
81+
ctx.registerBean("applicationEventMulticaster", ApplicationEventMulticaster.class, () -> multicaster);
82+
ctx.registerBean("conditionalListener", ConditionalListener.class);
83+
ctx.refresh();
84+
85+
assertListenerSelected(new SampleEvent(true), true);
86+
assertListenerSelected(new SampleEvent(false), false);
87+
}
88+
}
89+
90+
private void assertListenerSelected(SampleEvent event, boolean expected) {
91+
92+
var listeners = multicaster.getApplicationListeners(new PayloadApplicationEvent<>(this, event),
93+
ResolvableType.forClass(event.getClass()));
94+
95+
assertThat(listeners).hasSize(expected ? 1 : 0);
96+
}
97+
98+
@Component
99+
static class ConditionalListener {
100+
101+
@TransactionalEventListener(condition = "#event.supported")
102+
void on(SampleEvent event) {}
103+
}
104+
105+
@AllArgsConstructor
106+
static class SampleEvent {
107+
public boolean supported;
108+
}
64109
}

0 commit comments

Comments
 (0)