Browse Source

Avoid transaction listener execution without transaction management setup

Closes gh-32319
pull/32412/head
Juergen Hoeller 2 years ago
parent
commit
524588ef93
  1. 17
      spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java
  2. 10
      spring-context/src/main/java/org/springframework/context/event/EventListener.java
  3. 7
      spring-tx/src/main/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapter.java
  4. 14
      spring-tx/src/main/java/org/springframework/transaction/event/TransactionalEventListener.java
  5. 46
      spring-tx/src/test/java/org/springframework/transaction/event/TransactionalEventListenerTests.java

17
spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java

@ -91,6 +91,8 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe @@ -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 @@ -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 @@ -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 @@ -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

10
spring-context/src/main/java/org/springframework/context/event/EventListener.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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()").

7
spring-tx/src/main/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapter.java

@ -1,5 +1,5 @@ @@ -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 @@ -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 @@ -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 @@ -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");
}

14
spring-tx/src/main/java/org/springframework/transaction/event/TransactionalEventListener.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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 { @@ -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()").

46
spring-tx/src/test/java/org/springframework/transaction/event/TransactionalEventListenerTests.java

@ -269,8 +269,7 @@ class TransactionalEventListenerTests { @@ -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 { @@ -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 { @@ -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 {

Loading…
Cancel
Save