Skip to content

Commit 93e9d54

Browse files
committed
GH-305 - 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 5b13bce commit 93e9d54

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

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

Lines changed: 39 additions & 1 deletion
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.util.Collection;
1920
import java.util.List;
2021
import java.util.function.Consumer;
@@ -29,17 +30,20 @@
2930
import org.springframework.context.PayloadApplicationEvent;
3031
import org.springframework.context.event.AbstractApplicationEventMulticaster;
3132
import org.springframework.context.event.ApplicationEventMulticaster;
33+
import org.springframework.context.event.ApplicationListenerMethodAdapter;
3234
import org.springframework.core.ResolvableType;
3335
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
3436
import org.springframework.core.env.Environment;
3537
import org.springframework.lang.NonNull;
38+
import org.springframework.lang.Nullable;
3639
import org.springframework.modulith.events.core.EventPublication;
3740
import org.springframework.modulith.events.core.EventPublicationRegistry;
3841
import org.springframework.modulith.events.core.PublicationTargetIdentifier;
3942
import org.springframework.transaction.event.TransactionPhase;
4043
import org.springframework.transaction.event.TransactionalApplicationListener;
4144
import org.springframework.transaction.event.TransactionalEventListener;
4245
import org.springframework.util.Assert;
46+
import org.springframework.util.ReflectionUtils;
4347

4448
/**
4549
* An {@link ApplicationEventMulticaster} to register {@link EventPublication}s in an {@link EventPublicationRegistry}
@@ -56,11 +60,17 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv
5660
implements SmartInitializingSingleton {
5761

5862
private static final Logger LOGGER = LoggerFactory.getLogger(PersistentApplicationEventMulticaster.class);
63+
private static final Method SUPPORTS_METHOD = ReflectionUtils.findMethod(ApplicationListenerMethodAdapter.class,
64+
"shouldHandle", ApplicationEvent.class, Object[].class);
5965
static final String REPUBLISH_ON_RESTART = "spring.modulith.republish-outstanding-events-on-restart";
6066

6167
private final @NonNull Supplier<EventPublicationRegistry> registry;
6268
private final @NonNull Supplier<Environment> environment;
6369

70+
static {
71+
ReflectionUtils.makeAccessible(SUPPORTS_METHOD);
72+
}
73+
6474
/**
6575
* Creates a new {@link PersistentApplicationEventMulticaster} for the given {@link EventPublicationRegistry}.
6676
*
@@ -92,7 +102,7 @@ public void multicastEvent(ApplicationEvent event) {
92102
*/
93103
@Override
94104
@SuppressWarnings({ "unchecked", "rawtypes" })
95-
public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
105+
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
96106

97107
var type = eventType == null ? ResolvableType.forInstance(event) : eventType;
98108
var listeners = getApplicationListeners(event, type);
@@ -109,6 +119,22 @@ public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
109119
}
110120
}
111121

122+
/*
123+
* (non-Javadoc)
124+
* @see org.springframework.context.event.AbstractApplicationEventMulticaster#getApplicationListeners(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
125+
*/
126+
@Override
127+
protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event,
128+
ResolvableType eventType) {
129+
130+
Object eventToPersist = getEventToPersist(event);
131+
132+
return super.getApplicationListeners(event, eventType)
133+
.stream()
134+
.filter(it -> matches(event, eventToPersist, it))
135+
.toList();
136+
}
137+
112138
/*
113139
* (non-Javadoc)
114140
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
@@ -169,6 +195,18 @@ private static Object getEventToPersist(ApplicationEvent event) {
169195
: event;
170196
}
171197

198+
@SuppressWarnings("null")
199+
private static boolean matches(ApplicationEvent event, Object payload, ApplicationListener<?> listener) {
200+
201+
// Verify general listener matching by eagerly evaluating the condition
202+
if (!ApplicationListenerMethodAdapter.class.isInstance(listener)) {
203+
return true;
204+
}
205+
206+
return (boolean) ReflectionUtils.invokeMethod(SUPPORTS_METHOD, listener, event,
207+
new Object[] { payload });
208+
}
209+
172210
/**
173211
* First-class collection to work with transactional event listeners, i.e. {@link ApplicationListener} instances that
174212
* implement {@link TransactionalApplicationListener}.

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)