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.
This commit is contained in:
Oliver Drotbohm
2023-09-21 08:56:30 +02:00
parent f6a45c0355
commit 2148328c5d
2 changed files with 73 additions and 7 deletions

View File

@@ -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<EventPublicationRegistry> registry;
private final @NonNull Supplier<Environment> 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<EventPublication> 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;
}

View File

@@ -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;
}
}