Support for transactional listeners with reactive transactions
TransactionalApplicationListener and TransactionalEventListener automatically detect a reactive TransactionContext as the event source and register the synchronization accordingly. TransactionalEventPublisher is a convenient delegate for publishing corresponding events with the current TransactionContext as event source. This can also serve as a guideline for similar reactive event purposes. Closes gh-27515 Closes gh-21025 Closes gh-30244
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -32,12 +32,13 @@ import org.springframework.lang.Nullable;
|
||||
* allows you to prioritize that listener amongst other listeners running before or after
|
||||
* transaction completion.
|
||||
*
|
||||
* <p><b>NOTE: Transactional event listeners only work with thread-bound transactions
|
||||
* managed by a {@link org.springframework.transaction.PlatformTransactionManager
|
||||
* PlatformTransactionManager}.</b> A reactive transaction managed by a
|
||||
* {@link org.springframework.transaction.ReactiveTransactionManager ReactiveTransactionManager}
|
||||
* uses the Reactor context instead of thread-local variables, so from the perspective of
|
||||
* an event listener, there is no compatible active transaction that it can participate in.
|
||||
* <p>As of 6.1, transactional event listeners can work with thread-bound transactions managed
|
||||
* by a {@link org.springframework.transaction.PlatformTransactionManager} as well as reactive
|
||||
* transactions managed by a {@link org.springframework.transaction.ReactiveTransactionManager}.
|
||||
* For the former, listeners are guaranteed to see the current thread-bound transaction.
|
||||
* Since the latter uses the Reactor context instead of thread-local variables, the transaction
|
||||
* context needs to be included in the published event instance as the event source:
|
||||
* see {@link org.springframework.transaction.reactive.TransactionalEventPublisher}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Oliver Drotbohm
|
||||
@@ -60,6 +61,16 @@ public interface TransactionalApplicationListener<E extends ApplicationEvent>
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction-synchronized listeners do not support asynchronous execution,
|
||||
* only their target listener ({@link #processEvent}) potentially does.
|
||||
* @since 6.1
|
||||
*/
|
||||
@Override
|
||||
default boolean supportsAsyncExecution() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an identifier for the listener to be able to refer to it individually.
|
||||
* <p>It might be necessary for specific completion callback implementations
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -22,7 +22,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -128,11 +127,7 @@ public class TransactionalApplicationListenerAdapter<E extends ApplicationEvent>
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(E event) {
|
||||
if (TransactionSynchronizationManager.isSynchronizationActive() &&
|
||||
TransactionSynchronizationManager.isActualTransactionActive()) {
|
||||
TransactionSynchronizationManager.registerSynchronization(
|
||||
new TransactionalApplicationListenerSynchronization<>(event, this, this.callbacks));
|
||||
}
|
||||
TransactionalApplicationListenerSynchronization.register(event, this, this.callbacks);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.springframework.context.event.ApplicationListenerMethodAdapter;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.context.event.GenericApplicationListener;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -87,10 +86,10 @@ public class TransactionalApplicationListenerMethodAdapter extends ApplicationLi
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if (TransactionSynchronizationManager.isSynchronizationActive() &&
|
||||
TransactionSynchronizationManager.isActualTransactionActive()) {
|
||||
TransactionSynchronizationManager.registerSynchronization(
|
||||
new TransactionalApplicationListenerSynchronization<>(event, this, this.callbacks));
|
||||
if (TransactionalApplicationListenerSynchronization.register(event, this, this.callbacks)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Registered transaction synchronization for " + event);
|
||||
}
|
||||
}
|
||||
else if (this.annotation.fallbackExecution()) {
|
||||
if (this.annotation.phase() == TransactionPhase.AFTER_ROLLBACK && logger.isWarnEnabled()) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -18,19 +18,21 @@ package org.springframework.transaction.event;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.transaction.reactive.TransactionContext;
|
||||
|
||||
/**
|
||||
* {@link TransactionSynchronization} implementation for event processing with a
|
||||
* {@code TransactionSynchronization} implementations for event processing with a
|
||||
* {@link TransactionalApplicationListener}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.3
|
||||
* @param <E> the specific {@code ApplicationEvent} subclass to listen to
|
||||
*/
|
||||
class TransactionalApplicationListenerSynchronization<E extends ApplicationEvent>
|
||||
implements TransactionSynchronization {
|
||||
abstract class TransactionalApplicationListenerSynchronization<E extends ApplicationEvent> implements Ordered {
|
||||
|
||||
private final E event;
|
||||
|
||||
@@ -53,28 +55,11 @@ class TransactionalApplicationListenerSynchronization<E extends ApplicationEvent
|
||||
return this.listener.getOrder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeCommit(boolean readOnly) {
|
||||
if (this.listener.getTransactionPhase() == TransactionPhase.BEFORE_COMMIT) {
|
||||
processEventWithCallbacks();
|
||||
}
|
||||
public TransactionPhase getTransactionPhase() {
|
||||
return this.listener.getTransactionPhase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(int status) {
|
||||
TransactionPhase phase = this.listener.getTransactionPhase();
|
||||
if (phase == TransactionPhase.AFTER_COMMIT && status == STATUS_COMMITTED) {
|
||||
processEventWithCallbacks();
|
||||
}
|
||||
else if (phase == TransactionPhase.AFTER_ROLLBACK && status == STATUS_ROLLED_BACK) {
|
||||
processEventWithCallbacks();
|
||||
}
|
||||
else if (phase == TransactionPhase.AFTER_COMPLETION) {
|
||||
processEventWithCallbacks();
|
||||
}
|
||||
}
|
||||
|
||||
private void processEventWithCallbacks() {
|
||||
public void processEventWithCallbacks() {
|
||||
this.callbacks.forEach(callback -> callback.preProcessEvent(this.event));
|
||||
try {
|
||||
this.listener.processEvent(this.event);
|
||||
@@ -86,4 +71,94 @@ class TransactionalApplicationListenerSynchronization<E extends ApplicationEvent
|
||||
this.callbacks.forEach(callback -> callback.postProcessEvent(this.event, null));
|
||||
}
|
||||
|
||||
|
||||
public static <E extends ApplicationEvent> boolean register(
|
||||
E event, TransactionalApplicationListener<E> listener,
|
||||
List<TransactionalApplicationListener.SynchronizationCallback> callbacks) {
|
||||
|
||||
if (org.springframework.transaction.support.TransactionSynchronizationManager.isSynchronizationActive() &&
|
||||
org.springframework.transaction.support.TransactionSynchronizationManager.isActualTransactionActive()) {
|
||||
org.springframework.transaction.support.TransactionSynchronizationManager.registerSynchronization(
|
||||
new PlatformSynchronization<>(event, listener, callbacks));
|
||||
return true;
|
||||
}
|
||||
else if (event.getSource() instanceof TransactionContext txContext) {
|
||||
org.springframework.transaction.reactive.TransactionSynchronizationManager rtsm =
|
||||
new org.springframework.transaction.reactive.TransactionSynchronizationManager(txContext);
|
||||
if (rtsm.isSynchronizationActive() && rtsm.isActualTransactionActive()) {
|
||||
rtsm.registerSynchronization(new ReactiveSynchronization<>(event, listener, callbacks));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static class PlatformSynchronization<AE extends ApplicationEvent>
|
||||
extends TransactionalApplicationListenerSynchronization<AE>
|
||||
implements org.springframework.transaction.support.TransactionSynchronization {
|
||||
|
||||
public PlatformSynchronization(AE event, TransactionalApplicationListener<AE> listener,
|
||||
List<TransactionalApplicationListener.SynchronizationCallback> callbacks) {
|
||||
|
||||
super(event, listener, callbacks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeCommit(boolean readOnly) {
|
||||
if (getTransactionPhase() == TransactionPhase.BEFORE_COMMIT) {
|
||||
processEventWithCallbacks();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(int status) {
|
||||
TransactionPhase phase = getTransactionPhase();
|
||||
if (phase == TransactionPhase.AFTER_COMMIT && status == STATUS_COMMITTED) {
|
||||
processEventWithCallbacks();
|
||||
}
|
||||
else if (phase == TransactionPhase.AFTER_ROLLBACK && status == STATUS_ROLLED_BACK) {
|
||||
processEventWithCallbacks();
|
||||
}
|
||||
else if (phase == TransactionPhase.AFTER_COMPLETION) {
|
||||
processEventWithCallbacks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ReactiveSynchronization<AE extends ApplicationEvent>
|
||||
extends TransactionalApplicationListenerSynchronization<AE>
|
||||
implements org.springframework.transaction.reactive.TransactionSynchronization {
|
||||
|
||||
public ReactiveSynchronization(AE event, TransactionalApplicationListener<AE> listener,
|
||||
List<TransactionalApplicationListener.SynchronizationCallback> callbacks) {
|
||||
|
||||
super(event, listener, callbacks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> beforeCommit(boolean readOnly) {
|
||||
if (getTransactionPhase() == TransactionPhase.BEFORE_COMMIT) {
|
||||
return Mono.fromRunnable(this::processEventWithCallbacks);
|
||||
}
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> afterCompletion(int status) {
|
||||
TransactionPhase phase = getTransactionPhase();
|
||||
if (phase == TransactionPhase.AFTER_COMMIT && status == STATUS_COMMITTED) {
|
||||
return Mono.fromRunnable(this::processEventWithCallbacks);
|
||||
}
|
||||
else if (phase == TransactionPhase.AFTER_ROLLBACK && status == STATUS_ROLLED_BACK) {
|
||||
return Mono.fromRunnable(this::processEventWithCallbacks);
|
||||
}
|
||||
else if (phase == TransactionPhase.AFTER_COMPLETION) {
|
||||
return Mono.fromRunnable(this::processEventWithCallbacks);
|
||||
}
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -37,12 +37,13 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* method allows you to prioritize that listener amongst other listeners running before
|
||||
* or after transaction completion.
|
||||
*
|
||||
* <p><b>NOTE: Transactional event listeners only work with thread-bound transactions
|
||||
* managed by a {@link org.springframework.transaction.PlatformTransactionManager
|
||||
* PlatformTransactionManager}.</b> A reactive transaction managed by a
|
||||
* {@link org.springframework.transaction.ReactiveTransactionManager ReactiveTransactionManager}
|
||||
* uses the Reactor context instead of thread-local variables, so from the perspective of
|
||||
* an event listener, there is no compatible active transaction that it can participate in.
|
||||
* <p>As of 6.1, transactional event listeners can work with thread-bound transactions managed
|
||||
* by a {@link org.springframework.transaction.PlatformTransactionManager} as well as reactive
|
||||
* transactions managed by a {@link org.springframework.transaction.ReactiveTransactionManager}.
|
||||
* For the former, listeners are guaranteed to see the current thread-bound transaction.
|
||||
* Since the latter uses the Reactor context instead of thread-local variables, the transaction
|
||||
* context needs to be included in the published event instance as the event source:
|
||||
* see {@link org.springframework.transaction.reactive.TransactionalEventPublisher}.
|
||||
*
|
||||
* <p><strong>WARNING:</strong> if the {@code TransactionPhase} is set to
|
||||
* {@link TransactionPhase#AFTER_COMMIT AFTER_COMMIT} (the default),
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2002-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.transaction.reactive;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.PayloadApplicationEvent;
|
||||
|
||||
/**
|
||||
* A delegate for publishing transactional events in a reactive setup.
|
||||
* Includes the current Reactor-managed {@link TransactionContext} as
|
||||
* a source object for every {@link ApplicationEvent} to be published.
|
||||
*
|
||||
* <p>This delegate is just a convenience. The current {@link TransactionContext}
|
||||
* can be directly included as the event source as well, and then published
|
||||
* through an {@link ApplicationEventPublisher} such as the Spring
|
||||
* {@link org.springframework.context.ApplicationContext}:
|
||||
*
|
||||
* <pre class="code">
|
||||
* TransactionContextManager.currentContext()
|
||||
* .map(source -> new PayloadApplicationEvent<>(source, "myPayload"))
|
||||
* .doOnSuccess(this.eventPublisher::publishEvent)
|
||||
* </pre>
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 6.1
|
||||
* @see #publishEvent(Function)
|
||||
* @see #publishEvent(Object)
|
||||
* @see ApplicationEventPublisher
|
||||
*/
|
||||
public class TransactionalEventPublisher {
|
||||
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new delegate for publishing transactional events in a reactive setup.
|
||||
* @param eventPublisher the actual event publisher to use,
|
||||
* typically a Spring {@link org.springframework.context.ApplicationContext}
|
||||
*/
|
||||
public TransactionalEventPublisher(ApplicationEventPublisher eventPublisher) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Publish an event created through the given function which maps the transaction
|
||||
* source object (the {@link TransactionContext}) to the event instance.
|
||||
* @param eventCreationFunction a function mapping the source object to the event instance,
|
||||
* e.g. {@code source -> new PayloadApplicationEvent<>(source, "myPayload")}
|
||||
* @return the Reactor {@link Mono} for the transactional event publication
|
||||
*/
|
||||
public Mono<Void> publishEvent(Function<TransactionContext, ApplicationEvent> eventCreationFunction) {
|
||||
return TransactionContextManager.currentContext().map(eventCreationFunction)
|
||||
.doOnSuccess(this.eventPublisher::publishEvent).then();
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish an event created for the given payload.
|
||||
* @param payload the payload to publish as an event
|
||||
* @return the Reactor {@link Mono} for the transactional event publication
|
||||
*/
|
||||
public Mono<Void> publishEvent(Object payload) {
|
||||
if (payload instanceof ApplicationEvent) {
|
||||
return Mono.error(new IllegalArgumentException("Cannot publish ApplicationEvent with transactional " +
|
||||
"source - publish payload object or use publishEvent(Function<Object, ApplicationEvent>"));
|
||||
}
|
||||
return publishEvent(source -> new PayloadApplicationEvent<>(source, payload));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user