diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticaster.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticaster.java index 47356b981..f4c157d10 100644 --- a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticaster.java +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticaster.java @@ -15,6 +15,7 @@ */ package org.springframework.modulith.events.support; +import java.lang.reflect.Method; import java.time.Duration; import java.util.Collection; import java.util.List; @@ -31,6 +32,7 @@ import org.springframework.context.PayloadApplicationEvent; import org.springframework.context.event.AbstractApplicationEventMulticaster; import org.springframework.context.event.ApplicationEventMulticaster; +import org.springframework.context.event.ApplicationListenerMethodAdapter; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.env.Environment; @@ -46,6 +48,7 @@ import org.springframework.transaction.event.TransactionalApplicationListener; import org.springframework.transaction.event.TransactionalEventListener; import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; /** * An {@link ApplicationEventMulticaster} to register {@link EventPublication}s in an {@link EventPublicationRegistry} @@ -62,11 +65,17 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv implements IncompleteEventPublications, SmartInitializingSingleton { private static final Logger LOGGER = LoggerFactory.getLogger(PersistentApplicationEventMulticaster.class); + private static final Method SUPPORTS_METHOD = ReflectionUtils.findMethod(ApplicationListenerMethodAdapter.class, + "shouldHandle", ApplicationEvent.class, Object[].class); static final String REPUBLISH_ON_RESTART = "spring.modulith.republish-outstanding-events-on-restart"; private final @NonNull Supplier registry; private final @NonNull Supplier environment; + static { + ReflectionUtils.makeAccessible(SUPPORTS_METHOD); + } + /** * Creates a new {@link PersistentApplicationEventMulticaster} for the given {@link EventPublicationRegistry}. * @@ -127,14 +136,14 @@ protected Collection> getApplicationListeners(Application return super.getApplicationListeners(event, eventType) .stream() - .filter(it -> matches(eventToPersist, it)) + .filter(it -> matches(event, eventToPersist, it)) .toList(); } - /* - * (non-Javadoc) - * @see org.springframework.modulith.events.IncompleteEventPublications#resubmitIncompletePublications(java.util.function.Predicate) - */ + /* + * (non-Javadoc) + * @see org.springframework.modulith.events.IncompleteEventPublications#resubmitIncompletePublications(java.util.function.Predicate) + */ @Override public void resubmitIncompletePublications(Predicate filter) { doResubmitUncompletedPublicationsOlderThan(null); @@ -219,10 +228,22 @@ private static Object getEventToPersist(ApplicationEvent event) { : event; } - private static boolean matches(Object event, ApplicationListener listener) { + @SuppressWarnings("null") + private static boolean matches(ApplicationEvent event, Object payload, ApplicationListener listener) { + + // Verify general listener matching by eagerly evaluating the condition + if (ApplicationListenerMethodAdapter.class.isInstance(listener)) { + + boolean result = (boolean) ReflectionUtils.invokeMethod(SUPPORTS_METHOD, listener, event, + new Object[] { payload }); + + if (!result) { + return false; + } + } return ConditionalEventListener.class.isInstance(listener) - ? ConditionalEventListener.class.cast(listener).supports(event) + ? ConditionalEventListener.class.cast(listener).supports(payload) : true; } diff --git a/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticasterUnitTests.java b/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticasterUnitTests.java index 9448c0082..7f1a88f06 100644 --- a/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticasterUnitTests.java +++ b/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticasterUnitTests.java @@ -15,15 +15,25 @@ */ package org.springframework.modulith.events.support; +import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import lombok.AllArgsConstructor; + import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.context.PayloadApplicationEvent; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.event.ApplicationEventMulticaster; +import org.springframework.context.event.EventListenerMethodProcessor; +import org.springframework.core.ResolvableType; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.StandardEnvironment; import org.springframework.modulith.events.core.EventPublicationRegistry; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionalEventListener; /** * Unit tests for {@link PersistentApplicationEventMulticaster}. @@ -61,4 +71,39 @@ void triggersRepublicationIfExplicitlyEnabled() { verify(registry).findIncompletePublications(); } + + @Test // GH-277 + void honorsListenerCondition() throws Exception { + + try (var ctx = new AnnotationConfigApplicationContext()) { + + ctx.addBeanFactoryPostProcessor(new EventListenerMethodProcessor()); + ctx.registerBean("applicationEventMulticaster", ApplicationEventMulticaster.class, () -> multicaster); + ctx.registerBean("conditionalListener", ConditionalListener.class); + ctx.refresh(); + + assertListenerSelected(new SampleEvent(true), true); + assertListenerSelected(new SampleEvent(false), false); + } + } + + private void assertListenerSelected(SampleEvent event, boolean expected) { + + var listeners = multicaster.getApplicationListeners(new PayloadApplicationEvent<>(this, event), + ResolvableType.forClass(event.getClass())); + + assertThat(listeners).hasSize(expected ? 1 : 0); + } + + @Component + static class ConditionalListener { + + @TransactionalEventListener(condition = "#event.supported") + void on(SampleEvent event) {} + } + + @AllArgsConstructor + static class SampleEvent { + public boolean supported; + } }