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 beedebd1..d3748b45 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.util.Collection; import java.util.List; import java.util.function.Consumer; @@ -29,10 +30,12 @@ import org.springframework.context.ApplicationListener; 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; import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.modulith.events.core.EventPublication; import org.springframework.modulith.events.core.EventPublicationRegistry; import org.springframework.modulith.events.core.PublicationTargetIdentifier; @@ -40,6 +43,7 @@ import org.springframework.transaction.event.TransactionPhase; 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} @@ -56,11 +60,17 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv implements 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}. * @@ -92,7 +102,7 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv */ @Override @SuppressWarnings({ "unchecked", "rawtypes" }) - public void multicastEvent(ApplicationEvent event, ResolvableType eventType) { + public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) { var type = eventType == null ? ResolvableType.forInstance(event) : eventType; var listeners = getApplicationListeners(event, type); @@ -109,6 +119,22 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv } } + /* + * (non-Javadoc) + * @see org.springframework.context.event.AbstractApplicationEventMulticaster#getApplicationListeners(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType) + */ + @Override + protected Collection> getApplicationListeners(ApplicationEvent event, + ResolvableType eventType) { + + Object eventToPersist = getEventToPersist(event); + + return super.getApplicationListeners(event, eventType) + .stream() + .filter(it -> matches(event, eventToPersist, it)) + .toList(); + } + /* * (non-Javadoc) * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated() @@ -169,6 +195,18 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv : event; } + @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)) { + return true; + } + + return (boolean) ReflectionUtils.invokeMethod(SUPPORTS_METHOD, listener, event, + new Object[] { payload }); + } + /** * First-class collection to work with transactional event listeners, i.e. {@link ApplicationListener} instances that * implement {@link TransactionalApplicationListener}. 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 9448c008..7f1a88f0 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 @@ class PersistentApplicationEventMulticasterUnitTests { 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; + } }