Skip to content

Commit c9729bd

Browse files
committed
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] spring-projects/spring-framework#30399
1 parent b3b0661 commit c9729bd

File tree

2 files changed

+127
-0
lines changed

2 files changed

+127
-0
lines changed

spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticaster.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.modulith.events.support;
1717

18+
import java.lang.reflect.Field;
1819
import java.util.Collection;
1920
import java.util.List;
2021
import java.util.function.Consumer;
@@ -29,6 +30,7 @@
2930
import org.springframework.context.PayloadApplicationEvent;
3031
import org.springframework.context.event.AbstractApplicationEventMulticaster;
3132
import org.springframework.context.event.ApplicationEventMulticaster;
33+
import org.springframework.context.event.ApplicationListenerMethodAdapter;
3234
import org.springframework.core.ResolvableType;
3335
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
3436
import org.springframework.lang.NonNull;
@@ -39,6 +41,7 @@
3941
import org.springframework.transaction.event.TransactionalApplicationListener;
4042
import org.springframework.transaction.event.TransactionalEventListener;
4143
import org.springframework.util.Assert;
44+
import org.springframework.util.ReflectionUtils;
4245

4346
/**
4447
* An {@link ApplicationEventMulticaster} to register {@link EventPublication}s in an {@link EventPublicationRegistry}
@@ -55,9 +58,15 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv
5558
implements SmartInitializingSingleton {
5659

5760
private static final Logger LOGGER = LoggerFactory.getLogger(PersistentApplicationEventMulticaster.class);
61+
private static final Field DECLARED_EVENT_TYPES_FIELD = ReflectionUtils
62+
.findField(ApplicationListenerMethodAdapter.class, "declaredEventTypes");
5863

5964
private final @NonNull Supplier<EventPublicationRegistry> registry;
6065

66+
static {
67+
ReflectionUtils.makeAccessible(DECLARED_EVENT_TYPES_FIELD);
68+
}
69+
6170
/**
6271
* Creates a new {@link PersistentApplicationEventMulticaster} for the given {@link EventPublicationRegistry}.
6372
*
@@ -118,6 +127,34 @@ public void afterSingletonsInstantiated() {
118127
publications.forEach(this::invokeTargetListener);
119128
}
120129

130+
/**
131+
* Temporary workaround for an issue in Spring Framework that lets ApplicationListenerMethodAdapter match all generic
132+
* events with unresolved generics.
133+
*
134+
* @see <a href=
135+
* "https://github.com/spring-projects/spring-framework/issues/30399">https://github.com/spring-projects/spring-framework/issues/30399</a>
136+
*/
137+
@Override
138+
@SuppressWarnings("unchecked")
139+
protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, Class<?> sourceType) {
140+
141+
var result = super.supportsEvent(listener, eventType, sourceType);
142+
143+
if (!super.supportsEvent(listener, eventType, sourceType)
144+
|| !(listener instanceof ApplicationListenerMethodAdapter adapter)) {
145+
return result;
146+
}
147+
148+
var actualEventType = ResolvableType.forClass(PayloadApplicationEvent.class).isAssignableFrom(eventType)
149+
? eventType.getGeneric()
150+
: eventType;
151+
152+
var declaredEventTypes = (List<ResolvableType>) ReflectionUtils.getField(DECLARED_EVENT_TYPES_FIELD, adapter);
153+
154+
return declaredEventTypes.stream()
155+
.anyMatch(it -> it.isAssignableFrom(actualEventType.getRawClass()));
156+
}
157+
121158
private void invokeTargetListener(EventPublication publication) {
122159

123160
var listeners = new TransactionalEventListeners(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.modulith.events.support;
17+
18+
import static org.mockito.ArgumentMatchers.*;
19+
import static org.mockito.Mockito.*;
20+
21+
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.extension.ExtendWith;
23+
import org.springframework.beans.factory.annotation.Autowired;
24+
import org.springframework.context.ApplicationEvent;
25+
import org.springframework.context.ApplicationEventPublisher;
26+
import org.springframework.context.annotation.Bean;
27+
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.modulith.events.EventPublication;
29+
import org.springframework.modulith.events.EventPublicationRepository;
30+
import org.springframework.modulith.events.config.EnablePersistentDomainEvents;
31+
import org.springframework.stereotype.Component;
32+
import org.springframework.test.context.junit.jupiter.SpringExtension;
33+
import org.springframework.transaction.annotation.EnableTransactionManagement;
34+
import org.springframework.transaction.event.TransactionalEventListener;
35+
36+
/**
37+
* Integration test for {@link PersistentApplicationEventMulticaster}.
38+
*
39+
* @author Oliver Drotbohm
40+
*/
41+
@ExtendWith(SpringExtension.class)
42+
class PersistentApplicationEventMulticasterIntegrationTests {
43+
44+
@Configuration
45+
@EnableTransactionManagement
46+
@EnablePersistentDomainEvents
47+
static class TestConfiguration {
48+
49+
@Bean
50+
EventPublicationRepository repository() {
51+
return mock(EventPublicationRepository.class);
52+
}
53+
54+
@Bean
55+
SampleEventListener listener() {
56+
return new SampleEventListener();
57+
}
58+
}
59+
60+
@Autowired ApplicationEventPublisher publisher;
61+
@Autowired EventPublicationRepository repository;
62+
63+
@Test // GH-186
64+
void doesNotPublishGenericEventsToListeners() throws Exception {
65+
66+
publisher.publishEvent(new SomeGenericEvent<>());
67+
verify(repository, never()).create(any(EventPublication.class));
68+
69+
publisher.publishEvent(new SomeOtherEvent());
70+
verify(repository).create(any(EventPublication.class));
71+
}
72+
73+
@Component
74+
static class SampleEventListener {
75+
76+
@TransactionalEventListener
77+
void listener(SomeOtherEvent event) {}
78+
}
79+
80+
static class SomeGenericEvent<T> extends ApplicationEvent {
81+
82+
private static final long serialVersionUID = -4054955298417761460L;
83+
84+
public SomeGenericEvent() {
85+
super(new Object());
86+
}
87+
}
88+
89+
static class SomeOtherEvent {}
90+
}

0 commit comments

Comments
 (0)