GH-186 - Workaround for invalid application listener matching in AbstractApplicationEventMulticaster.

AbstractApplicationEventMulticaster.supportsEvent(…) currently doesn't properly match unresolved, generic ApplicationEvents (see [0]). We now work around this problem by additionally matching the raw event types in a custom override of supportsEvent(…).

[0] https://github.com/spring-projects/spring-framework/issues/30399
This commit is contained in:
Oliver Drotbohm
2023-04-29 21:15:13 +02:00
parent b3b06614f9
commit c9729bded0
2 changed files with 127 additions and 0 deletions

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.modulith.events.support;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
@@ -29,6 +30,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.lang.NonNull;
@@ -39,6 +41,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}
@@ -55,9 +58,15 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv
implements SmartInitializingSingleton {
private static final Logger LOGGER = LoggerFactory.getLogger(PersistentApplicationEventMulticaster.class);
private static final Field DECLARED_EVENT_TYPES_FIELD = ReflectionUtils
.findField(ApplicationListenerMethodAdapter.class, "declaredEventTypes");
private final @NonNull Supplier<EventPublicationRegistry> registry;
static {
ReflectionUtils.makeAccessible(DECLARED_EVENT_TYPES_FIELD);
}
/**
* Creates a new {@link PersistentApplicationEventMulticaster} for the given {@link EventPublicationRegistry}.
*
@@ -118,6 +127,34 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv
publications.forEach(this::invokeTargetListener);
}
/**
* Temporary workaround for an issue in Spring Framework that lets ApplicationListenerMethodAdapter match all generic
* events with unresolved generics.
*
* @see <a href=
* "https://github.com/spring-projects/spring-framework/issues/30399">https://github.com/spring-projects/spring-framework/issues/30399</a>
*/
@Override
@SuppressWarnings("unchecked")
protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, Class<?> sourceType) {
var result = super.supportsEvent(listener, eventType, sourceType);
if (!super.supportsEvent(listener, eventType, sourceType)
|| !(listener instanceof ApplicationListenerMethodAdapter adapter)) {
return result;
}
var actualEventType = ResolvableType.forClass(PayloadApplicationEvent.class).isAssignableFrom(eventType)
? eventType.getGeneric()
: eventType;
var declaredEventTypes = (List<ResolvableType>) ReflectionUtils.getField(DECLARED_EVENT_TYPES_FIELD, adapter);
return declaredEventTypes.stream()
.anyMatch(it -> it.isAssignableFrom(actualEventType.getRawClass()));
}
private void invokeTargetListener(EventPublication publication) {
var listeners = new TransactionalEventListeners(

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.events.support;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.modulith.events.EventPublication;
import org.springframework.modulith.events.EventPublicationRepository;
import org.springframework.modulith.events.config.EnablePersistentDomainEvents;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.event.TransactionalEventListener;
/**
* Integration test for {@link PersistentApplicationEventMulticaster}.
*
* @author Oliver Drotbohm
*/
@ExtendWith(SpringExtension.class)
class PersistentApplicationEventMulticasterIntegrationTests {
@Configuration
@EnableTransactionManagement
@EnablePersistentDomainEvents
static class TestConfiguration {
@Bean
EventPublicationRepository repository() {
return mock(EventPublicationRepository.class);
}
@Bean
SampleEventListener listener() {
return new SampleEventListener();
}
}
@Autowired ApplicationEventPublisher publisher;
@Autowired EventPublicationRepository repository;
@Test // GH-186
void doesNotPublishGenericEventsToListeners() throws Exception {
publisher.publishEvent(new SomeGenericEvent<>());
verify(repository, never()).create(any(EventPublication.class));
publisher.publishEvent(new SomeOtherEvent());
verify(repository).create(any(EventPublication.class));
}
@Component
static class SampleEventListener {
@TransactionalEventListener
void listener(SomeOtherEvent event) {}
}
static class SomeGenericEvent<T> extends ApplicationEvent {
private static final long serialVersionUID = -4054955298417761460L;
public SomeGenericEvent() {
super(new Object());
}
}
static class SomeOtherEvent {}
}