From 2148328c5de3ea34dd076a2320261f90de93cd69 Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Thu, 21 Sep 2023 08:56:30 +0200 Subject: [PATCH] GH-277 - Multicaster now honors listener condition. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- ...PersistentApplicationEventMulticaster.java | 35 ++++++++++++--- ...tApplicationEventMulticasterUnitTests.java | 45 +++++++++++++++++++ 2 files changed, 73 insertions(+), 7 deletions(-) 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 47356b98..f4c157d1 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.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; @@ -46,6 +48,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} @@ -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 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv 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 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv : 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 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; + } }