Skip to content

Commit e38c964

Browse files
committed
Enhance PersistentApplicationEventMulticaster to support externalization configuration and add warning for events published outside transaction context.
Signed-off-by: yunhobb <a01049048063@gmail.com>
1 parent b6c2d00 commit e38c964

File tree

4 files changed

+64
-8
lines changed

4 files changed

+64
-8
lines changed

spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/config/EventPublicationAutoConfiguration.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.context.annotation.Import;
4040
import org.springframework.context.annotation.Role;
4141
import org.springframework.core.env.Environment;
42+
import org.springframework.modulith.events.EventExternalizationConfiguration;
4243
import org.springframework.modulith.events.config.EventPublicationAutoConfiguration.AsyncEnablingConfiguration;
4344
import org.springframework.modulith.events.core.DefaultEventPublicationRegistry;
4445
import org.springframework.modulith.events.core.EventPublicationRegistry;
@@ -74,9 +75,11 @@ DefaultEventPublicationRegistry eventPublicationRegistry(EventPublicationReposit
7475
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
7576
@ConditionalOnBean(EventPublicationRegistry.class)
7677
static PersistentApplicationEventMulticaster applicationEventMulticaster(
77-
ObjectFactory<EventPublicationRegistry> eventPublicationRegistry, ObjectFactory<Environment> environment) {
78+
ObjectFactory<EventPublicationRegistry> eventPublicationRegistry, ObjectFactory<Environment> environment,
79+
ObjectProvider<EventExternalizationConfiguration> externalizationConfiguration) {
7880

79-
return EventPublicationConfiguration.applicationEventMulticaster(eventPublicationRegistry, environment);
81+
return EventPublicationConfiguration.applicationEventMulticaster(eventPublicationRegistry, environment,
82+
externalizationConfiguration);
8083
}
8184

8285
@Bean

spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/config/EventPublicationConfiguration.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.context.annotation.Configuration;
2525
import org.springframework.context.annotation.Role;
2626
import org.springframework.core.env.Environment;
27+
import org.springframework.modulith.events.EventExternalizationConfiguration;
2728
import org.springframework.modulith.events.core.DefaultEventPublicationRegistry;
2829
import org.springframework.modulith.events.core.EventPublicationRegistry;
2930
import org.springframework.modulith.events.core.EventPublicationRepository;
@@ -51,10 +52,12 @@ DefaultEventPublicationRegistry eventPublicationRegistry(EventPublicationReposit
5152
@Bean
5253
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
5354
static PersistentApplicationEventMulticaster applicationEventMulticaster(
54-
ObjectFactory<EventPublicationRegistry> eventPublicationRegistry, ObjectFactory<Environment> environment) {
55+
ObjectFactory<EventPublicationRegistry> eventPublicationRegistry, ObjectFactory<Environment> environment,
56+
ObjectProvider<EventExternalizationConfiguration> externalizationConfiguration) {
5557

5658
return new PersistentApplicationEventMulticaster(() -> eventPublicationRegistry.getObject(),
57-
() -> environment.getObject());
59+
() -> environment.getObject(),
60+
() -> externalizationConfiguration.getIfAvailable(EventExternalizationConfiguration::disabled));
5861
}
5962

6063
@Bean

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

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.core.ResolvableType;
3838
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
3939
import org.springframework.core.env.Environment;
40+
import org.springframework.modulith.events.EventExternalizationConfiguration;
4041
import org.springframework.modulith.events.EventPublication;
4142
import org.springframework.modulith.events.FailedEventPublications;
4243
import org.springframework.modulith.events.IncompleteEventPublications;
@@ -47,6 +48,7 @@
4748
import org.springframework.modulith.events.core.TargetEventPublication;
4849
import org.springframework.transaction.event.TransactionPhase;
4950
import org.springframework.transaction.event.TransactionalApplicationListener;
51+
import org.springframework.transaction.support.TransactionSynchronizationManager;
5052
import org.springframework.util.Assert;
5153

5254
/**
@@ -58,6 +60,7 @@
5860
* for incomplete publications.
5961
*
6062
* @author Oliver Drotbohm
63+
* @author Yunho Jung
6164
* @see CompletionRegisteringAdvisor
6265
*/
6366
public class PersistentApplicationEventMulticaster extends AbstractApplicationEventMulticaster
@@ -70,21 +73,26 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv
7073

7174
private final @NonNull Supplier<EventPublicationRegistry> registry;
7275
private final @NonNull Supplier<Environment> environment;
76+
private final @NonNull Supplier<EventExternalizationConfiguration> externalizationConfiguration;
7377

7478
/**
7579
* Creates a new {@link PersistentApplicationEventMulticaster} for the given {@link EventPublicationRegistry}.
7680
*
7781
* @param registry must not be {@literal null}.
7882
* @param environment must not be {@literal null}.
83+
* @param externalizationConfiguration must not be {@literal null}.
7984
*/
8085
public PersistentApplicationEventMulticaster(Supplier<EventPublicationRegistry> registry,
81-
Supplier<Environment> environment) {
86+
Supplier<Environment> environment,
87+
Supplier<EventExternalizationConfiguration> externalizationConfiguration) {
8288

8389
Assert.notNull(registry, "EventPublicationRegistry must not be null!");
8490
Assert.notNull(environment, "Environment must not be null!");
91+
Assert.notNull(externalizationConfiguration, "EventExternalizationConfiguration must not be null!");
8592

8693
this.registry = registry;
8794
this.environment = environment;
95+
this.externalizationConfiguration = externalizationConfiguration;
8896
}
8997

9098
/*
@@ -111,8 +119,13 @@ public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType even
111119
return;
112120
}
113121

114-
new TransactionalEventListeners(listeners)
115-
.ifPresent(it -> storePublications(it, getEventToPersist(event)));
122+
var eventToPersist = getEventToPersist(event);
123+
var transactionalListeners = new TransactionalEventListeners(listeners);
124+
125+
// Detect events configured for externalization published outside transaction context
126+
detectEventPublishedOutsideTransaction(transactionalListeners, eventToPersist);
127+
128+
transactionalListeners.ifPresent(it -> storePublications(it, eventToPersist));
116129

117130
for (ApplicationListener listener : listeners) {
118131
listener.onApplicationEvent(event);
@@ -273,6 +286,41 @@ private static boolean invokeShouldHandle(ApplicationListener<?> candidate, Appl
273286
return true;
274287
}
275288

289+
/**
290+
* Detects if an event selected for externalization is published outside a transaction context.
291+
* If detected, logs a warning message to help developers identify the problem.
292+
*
293+
* @param transactionalListeners the transactional event listeners
294+
* @param event the event being published
295+
*/
296+
private void detectEventPublishedOutsideTransaction(TransactionalEventListeners transactionalListeners,
297+
Object event) {
298+
299+
// Transaction is active, no problem
300+
if (TransactionSynchronizationManager.isActualTransactionActive()) {
301+
return;
302+
}
303+
304+
// No transactional listeners, nothing to check
305+
if (!transactionalListeners.hasListeners()) {
306+
return;
307+
}
308+
309+
// Check if the event is configured for externalization
310+
var config = externalizationConfiguration.get();
311+
if (!config.supports(event)) {
312+
return;
313+
}
314+
315+
// Issue a warning log hinting at the problem
316+
LOGGER.warn(
317+
"Event {} is configured for externalization but published outside a transaction context. "
318+
+ "Event externalization requires a transactional context to work properly. "
319+
+ "The event will not be persisted to the event publication registry and externalization will not be triggered. "
320+
+ "Consider publishing this event from a @Transactional method.",
321+
event.getClass().getName());
322+
}
323+
276324
/**
277325
* First-class collection to work with transactional event listeners, i.e. {@link ApplicationListener} instances that
278326
* implement {@link TransactionalApplicationListener}.

spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticasterUnitTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.core.ResolvableType;
3232
import org.springframework.core.env.MapPropertySource;
3333
import org.springframework.core.env.StandardEnvironment;
34+
import org.springframework.modulith.events.EventExternalizationConfiguration;
3435
import org.springframework.modulith.events.core.EventPublicationRegistry;
3536
import org.springframework.stereotype.Component;
3637
import org.springframework.transaction.event.TransactionPhase;
@@ -51,7 +52,8 @@ class PersistentApplicationEventMulticasterUnitTests {
5152

5253
@BeforeEach
5354
void setUp() {
54-
this.multicaster = new PersistentApplicationEventMulticaster(() -> registry, () -> environment);
55+
this.multicaster = new PersistentApplicationEventMulticaster(() -> registry, () -> environment,
56+
EventExternalizationConfiguration::disabled);
5557
}
5658

5759
@Test // GH-240, GH-251

0 commit comments

Comments
 (0)