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.
This commit is contained in:
@@ -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<EventPublicationRegistry> registry;
|
||||
private final @NonNull Supplier<Environment> 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<ApplicationListener<?>> 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}.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user