Avoid transaction listener execution without transaction management setup
Closes gh-32319
This commit is contained in:
@@ -91,6 +91,8 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
|
||||
@Nullable
|
||||
private final String condition;
|
||||
|
||||
private final boolean defaultExecution;
|
||||
|
||||
private final int order;
|
||||
|
||||
@Nullable
|
||||
@@ -119,6 +121,7 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
|
||||
EventListener ann = AnnotatedElementUtils.findMergedAnnotation(this.targetMethod, EventListener.class);
|
||||
this.declaredEventTypes = resolveDeclaredEventTypes(method, ann);
|
||||
this.condition = (ann != null ? ann.condition() : null);
|
||||
this.defaultExecution = (ann == null || ann.defaultExecution());
|
||||
this.order = resolveOrder(this.targetMethod);
|
||||
String id = (ann != null ? ann.id() : "");
|
||||
this.listenerId = (!id.isEmpty() ? id : null);
|
||||
@@ -166,7 +169,9 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
processEvent(event);
|
||||
if (isDefaultExecution()) {
|
||||
processEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -227,6 +232,16 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
|
||||
return ClassUtils.getQualifiedMethodName(method) + sj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether default execution is applicable for the target listener.
|
||||
* @since 6.2
|
||||
* @see #onApplicationEvent
|
||||
* @see EventListener#defaultExecution()
|
||||
*/
|
||||
protected boolean isDefaultExecution() {
|
||||
return this.defaultExecution;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process the specified {@link ApplicationEvent}, checking if the condition
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -132,6 +132,14 @@ public @interface EventListener {
|
||||
*/
|
||||
String condition() default "";
|
||||
|
||||
/**
|
||||
* Whether the event should be handled by default, without any special
|
||||
* pre-conditions such as an active transaction. Declared here for overriding
|
||||
* in composed annotations such as {@code TransactionalEventListener}.
|
||||
* @since 6.2
|
||||
*/
|
||||
boolean defaultExecution() default true;
|
||||
|
||||
/**
|
||||
* An optional identifier for the listener, defaulting to the fully-qualified
|
||||
* signature of the declaring method (e.g. "mypackage.MyClass.myMethod()").
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -49,8 +49,6 @@ public class TransactionalApplicationListenerMethodAdapter extends ApplicationLi
|
||||
|
||||
private final TransactionPhase transactionPhase;
|
||||
|
||||
private final boolean fallbackExecution;
|
||||
|
||||
private final List<SynchronizationCallback> callbacks = new CopyOnWriteArrayList<>();
|
||||
|
||||
|
||||
@@ -68,7 +66,6 @@ public class TransactionalApplicationListenerMethodAdapter extends ApplicationLi
|
||||
throw new IllegalStateException("No TransactionalEventListener annotation found on method: " + method);
|
||||
}
|
||||
this.transactionPhase = eventAnn.phase();
|
||||
this.fallbackExecution = eventAnn.fallbackExecution();
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +88,7 @@ public class TransactionalApplicationListenerMethodAdapter extends ApplicationLi
|
||||
logger.debug("Registered transaction synchronization for " + event);
|
||||
}
|
||||
}
|
||||
else if (this.fallbackExecution) {
|
||||
else if (isDefaultExecution()) {
|
||||
if (getTransactionPhase() == TransactionPhase.AFTER_ROLLBACK && logger.isWarnEnabled()) {
|
||||
logger.warn("Processing " + event + " as a fallback execution on AFTER_ROLLBACK phase");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -77,11 +77,6 @@ public @interface TransactionalEventListener {
|
||||
*/
|
||||
TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;
|
||||
|
||||
/**
|
||||
* Whether the event should be handled if no transaction is running.
|
||||
*/
|
||||
boolean fallbackExecution() default false;
|
||||
|
||||
/**
|
||||
* Alias for {@link #classes}.
|
||||
*/
|
||||
@@ -107,6 +102,13 @@ public @interface TransactionalEventListener {
|
||||
@AliasFor(annotation = EventListener.class, attribute = "condition")
|
||||
String condition() default "";
|
||||
|
||||
/**
|
||||
* Whether the event should be handled if no transaction is running.
|
||||
* @see EventListener#defaultExecution()
|
||||
*/
|
||||
@AliasFor(annotation = EventListener.class, attribute = "defaultExecution")
|
||||
boolean fallbackExecution() default false;
|
||||
|
||||
/**
|
||||
* An optional identifier for the listener, defaulting to the fully-qualified
|
||||
* signature of the declaring method (e.g. "mypackage.MyClass.myMethod()").
|
||||
|
||||
@@ -269,8 +269,7 @@ class TransactionalEventListenerTests {
|
||||
|
||||
@Test
|
||||
void noTransaction() {
|
||||
load(BeforeCommitTestListener.class, AfterCompletionTestListener.class,
|
||||
AfterCompletionExplicitTestListener.class);
|
||||
load(BeforeCommitTestListener.class, AfterCompletionTestListener.class, AfterCompletionExplicitTestListener.class);
|
||||
this.context.publishEvent("test");
|
||||
getEventCollector().assertTotalEventsCount(0);
|
||||
}
|
||||
@@ -318,6 +317,24 @@ class TransactionalEventListenerTests {
|
||||
getEventCollector().assertTotalEventsCount(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noTransactionManagementWithFallbackExecution() {
|
||||
doLoad(PlainConfiguration.class, FallbackExecutionTestListener.class);
|
||||
this.context.publishEvent("test");
|
||||
this.eventCollector.assertEvents(EventCollector.BEFORE_COMMIT, "test");
|
||||
this.eventCollector.assertEvents(EventCollector.AFTER_COMMIT, "test");
|
||||
this.eventCollector.assertEvents(EventCollector.AFTER_ROLLBACK, "test");
|
||||
this.eventCollector.assertEvents(EventCollector.AFTER_COMPLETION, "test");
|
||||
getEventCollector().assertTotalEventsCount(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noTransactionManagementWithoutFallbackExecution() {
|
||||
doLoad(PlainConfiguration.class, BeforeCommitTestListener.class, AfterCommitMetaAnnotationTestListener.class);
|
||||
this.context.publishEvent("test");
|
||||
this.eventCollector.assertNoEventReceived();
|
||||
}
|
||||
|
||||
@Test
|
||||
void conditionFoundOnTransactionalEventListener() {
|
||||
load(ImmediateTestListener.class);
|
||||
@@ -401,6 +418,31 @@ class TransactionalEventListenerTests {
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class PlainConfiguration {
|
||||
|
||||
@Bean
|
||||
public EventCollector eventCollector() {
|
||||
return new EventCollector();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestBean testBean(ApplicationEventPublisher eventPublisher) {
|
||||
return new TestBean(eventPublisher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CallCountingTransactionManager transactionManager() {
|
||||
return new CallCountingTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TransactionTemplate transactionTemplate() {
|
||||
return new TransactionTemplate(transactionManager());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class MulticasterWithCustomExecutor {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user