Skip to content

Commit a15f5f6

Browse files
committed
Add global error handler for transactional event listener
1 parent fbed55e commit a15f5f6

File tree

5 files changed

+166
-9
lines changed

5 files changed

+166
-9
lines changed

spring-tx/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import org.springframework.core.type.AnnotationMetadata;
3131
import org.springframework.transaction.TransactionManager;
3232
import org.springframework.transaction.config.TransactionManagementConfigUtils;
33-
import org.springframework.transaction.config.TransactionalEventErrorHandler;
33+
import org.springframework.transaction.config.GlobalTransactionalEventErrorHandler;
3434
import org.springframework.transaction.event.TransactionalEventListenerFactory;
3535
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
3636
import org.springframework.transaction.interceptor.TransactionAttributeSource;
@@ -94,7 +94,10 @@ public TransactionAttributeSource transactionAttributeSource() {
9494

9595
@Bean(name = TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME)
9696
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
97-
public static TransactionalEventListenerFactory transactionalEventListenerFactory(@Nullable TransactionalEventErrorHandler errorHandler) {
97+
public static TransactionalEventListenerFactory transactionalEventListenerFactory(@Nullable GlobalTransactionalEventErrorHandler errorHandler) {
98+
if (errorHandler == null) {
99+
return new RestrictedTransactionalEventListenerFactory();
100+
}
98101
return new RestrictedTransactionalEventListenerFactory(errorHandler);
99102
}
100103

spring-tx/src/main/java/org/springframework/transaction/annotation/RestrictedTransactionalEventListenerFactory.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@
1818

1919
import java.lang.reflect.Method;
2020

21-
import org.jspecify.annotations.Nullable;
2221
import org.springframework.context.ApplicationListener;
2322
import org.springframework.core.annotation.AnnotatedElementUtils;
24-
import org.springframework.transaction.config.TransactionalEventErrorHandler;
23+
import org.springframework.transaction.config.GlobalTransactionalEventErrorHandler;
2524
import org.springframework.transaction.event.TransactionalEventListenerFactory;
2625

2726
/**
@@ -37,7 +36,11 @@
3736
*/
3837
public class RestrictedTransactionalEventListenerFactory extends TransactionalEventListenerFactory {
3938

40-
public RestrictedTransactionalEventListenerFactory(@Nullable TransactionalEventErrorHandler errorHandler) {
39+
public RestrictedTransactionalEventListenerFactory() {
40+
super();
41+
}
42+
43+
public RestrictedTransactionalEventListenerFactory(GlobalTransactionalEventErrorHandler errorHandler) {
4144
super(errorHandler);
4245
}
4346

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.springframework.transaction.config;
2+
3+
import org.jspecify.annotations.Nullable;
4+
import org.springframework.context.ApplicationEvent;
5+
import org.springframework.transaction.event.TransactionalApplicationListener;
6+
7+
public abstract class GlobalTransactionalEventErrorHandler implements TransactionalApplicationListener.SynchronizationCallback {
8+
9+
public abstract void handle(ApplicationEvent event, @Nullable Throwable ex);
10+
11+
@Override
12+
public void postProcessEvent(ApplicationEvent event, @Nullable Throwable ex) {
13+
if (ex != null) {
14+
handle(event, ex);
15+
}
16+
}
17+
18+
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import org.springframework.context.event.EventListenerFactory;
2424
import org.springframework.core.Ordered;
2525
import org.springframework.core.annotation.AnnotatedElementUtils;
26-
import org.springframework.transaction.config.TransactionalEventErrorHandler;
26+
import org.springframework.transaction.config.GlobalTransactionalEventErrorHandler;
2727

2828
/**
2929
* {@link EventListenerFactory} implementation that handles {@link TransactionalEventListener}
@@ -37,11 +37,11 @@ public class TransactionalEventListenerFactory implements EventListenerFactory,
3737

3838
private int order = 50;
3939

40-
private @Nullable TransactionalEventErrorHandler errorHandler;
40+
private @Nullable GlobalTransactionalEventErrorHandler errorHandler;
4141

4242
public TransactionalEventListenerFactory() { }
4343

44-
public TransactionalEventListenerFactory(@Nullable TransactionalEventErrorHandler errorHandler) {
44+
public TransactionalEventListenerFactory(GlobalTransactionalEventErrorHandler errorHandler) {
4545
this.errorHandler = errorHandler;
4646
}
4747

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

+134-1
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626
import java.util.List;
2727
import java.util.Map;
2828

29+
import org.jspecify.annotations.Nullable;
2930
import org.junit.jupiter.api.AfterEach;
3031
import org.junit.jupiter.api.Test;
3132

3233
import org.springframework.beans.factory.annotation.Autowired;
34+
import org.springframework.context.ApplicationEvent;
3335
import org.springframework.context.ApplicationEventPublisher;
3436
import org.springframework.context.ConfigurableApplicationContext;
3537
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@@ -43,6 +45,7 @@
4345
import org.springframework.transaction.annotation.EnableTransactionManagement;
4446
import org.springframework.transaction.annotation.Propagation;
4547
import org.springframework.transaction.annotation.Transactional;
48+
import org.springframework.transaction.config.GlobalTransactionalEventErrorHandler;
4649
import org.springframework.transaction.support.TransactionSynchronization;
4750
import org.springframework.transaction.support.TransactionSynchronizationManager;
4851
import org.springframework.transaction.support.TransactionTemplate;
@@ -369,6 +372,45 @@ void conditionFoundOnMetaAnnotation() {
369372
getEventCollector().assertNoEventReceived();
370373
}
371374

375+
@Test
376+
void afterCommitThrowException() {
377+
doLoad(HandlerConfiguration.class, AfterCommitErrorHandlerTestListener.class);
378+
this.transactionTemplate.execute(status -> {
379+
getContext().publishEvent("test");
380+
getEventCollector().assertNoEventReceived();
381+
return null;
382+
});
383+
getEventCollector().assertEvents(EventCollector.AFTER_COMMIT, "test");
384+
getEventCollector().assertEvents(EventCollector.HANDLE_ERROR, "HANDLE_ERROR");
385+
getEventCollector().assertTotalEventsCount(2);
386+
}
387+
388+
@Test
389+
void afterRollbackThrowException() {
390+
doLoad(HandlerConfiguration.class, AfterRollbackErrorHandlerTestListener.class);
391+
this.transactionTemplate.execute(status -> {
392+
getContext().publishEvent("test");
393+
getEventCollector().assertNoEventReceived();
394+
status.setRollbackOnly();
395+
return null;
396+
});
397+
getEventCollector().assertEvents(EventCollector.AFTER_ROLLBACK, "test");
398+
getEventCollector().assertEvents(EventCollector.HANDLE_ERROR, "HANDLE_ERROR");
399+
getEventCollector().assertTotalEventsCount(2);
400+
}
401+
402+
@Test
403+
void afterCompletionThrowException() {
404+
doLoad(HandlerConfiguration.class, AfterCompletionErrorHandlerTestListener.class);
405+
this.transactionTemplate.execute(status -> {
406+
getContext().publishEvent("test");
407+
getEventCollector().assertNoEventReceived();
408+
return null;
409+
});
410+
getEventCollector().assertEvents(EventCollector.AFTER_COMPLETION, "test");
411+
getEventCollector().assertEvents(EventCollector.HANDLE_ERROR, "HANDLE_ERROR");
412+
getEventCollector().assertTotalEventsCount(2);
413+
}
372414

373415
protected EventCollector getEventCollector() {
374416
return this.eventCollector;
@@ -442,6 +484,36 @@ public TransactionTemplate transactionTemplate() {
442484
}
443485
}
444486

487+
@Configuration
488+
@EnableTransactionManagement
489+
static class HandlerConfiguration {
490+
491+
@Bean
492+
public EventCollector eventCollector() {
493+
return new EventCollector();
494+
}
495+
496+
@Bean
497+
public TestBean testBean(ApplicationEventPublisher eventPublisher) {
498+
return new TestBean(eventPublisher);
499+
}
500+
501+
@Bean
502+
public CallCountingTransactionManager transactionManager() {
503+
return new CallCountingTransactionManager();
504+
}
505+
506+
@Bean
507+
public TransactionTemplate transactionTemplate() {
508+
return new TransactionTemplate(transactionManager());
509+
}
510+
511+
@Bean
512+
public AfterRollbackErrorHandler errorHandler(ApplicationEventPublisher eventPublisher) {
513+
return new AfterRollbackErrorHandler(eventPublisher);
514+
}
515+
}
516+
445517

446518
@Configuration
447519
static class MulticasterWithCustomExecutor {
@@ -467,7 +539,9 @@ static class EventCollector {
467539

468540
public static final String AFTER_ROLLBACK = "AFTER_ROLLBACK";
469541

470-
public static final String[] ALL_PHASES = {IMMEDIATELY, BEFORE_COMMIT, AFTER_COMMIT, AFTER_ROLLBACK};
542+
public static final String HANDLE_ERROR = "HANDLE_ERROR";
543+
544+
public static final String[] ALL_PHASES = {IMMEDIATELY, BEFORE_COMMIT, AFTER_COMMIT, AFTER_ROLLBACK, HANDLE_ERROR};
471545

472546
private final MultiValueMap<String, Object> events = new LinkedMultiValueMap<>();
473547

@@ -677,6 +751,51 @@ public void handleAfterCommit(String data) {
677751
}
678752

679753

754+
@Component
755+
static class AfterCommitErrorHandlerTestListener extends BaseTransactionalTestListener {
756+
757+
@TransactionalEventListener(phase = AFTER_COMMIT, condition = "!'HANDLE_ERROR'.equals(#data)")
758+
public void handleBeforeCommit(String data) {
759+
handleEvent(EventCollector.AFTER_COMMIT, data);
760+
throw new IllegalStateException("test");
761+
}
762+
763+
@EventListener(condition = "'HANDLE_ERROR'.equals(#data)")
764+
public void handleImmediately(String data) {
765+
handleEvent(EventCollector.HANDLE_ERROR, data);
766+
}
767+
}
768+
769+
@Component
770+
static class AfterRollbackErrorHandlerTestListener extends BaseTransactionalTestListener {
771+
772+
@TransactionalEventListener(phase = AFTER_ROLLBACK, condition = "!'HANDLE_ERROR'.equals(#data)")
773+
public void handleBeforeCommit(String data) {
774+
handleEvent(EventCollector.AFTER_ROLLBACK, data);
775+
throw new IllegalStateException("test");
776+
}
777+
778+
@EventListener(condition = "'HANDLE_ERROR'.equals(#data)")
779+
public void handleImmediately(String data) {
780+
handleEvent(EventCollector.HANDLE_ERROR, data);
781+
}
782+
}
783+
784+
@Component
785+
static class AfterCompletionErrorHandlerTestListener extends BaseTransactionalTestListener {
786+
787+
@TransactionalEventListener(phase = AFTER_COMPLETION, condition = "!'HANDLE_ERROR'.equals(#data)")
788+
public void handleBeforeCommit(String data) {
789+
handleEvent(EventCollector.AFTER_COMPLETION, data);
790+
throw new IllegalStateException("test");
791+
}
792+
793+
@EventListener(condition = "'HANDLE_ERROR'.equals(#data)")
794+
public void handleImmediately(String data) {
795+
handleEvent(EventCollector.HANDLE_ERROR, data);
796+
}
797+
}
798+
680799
static class EventTransactionSynchronization implements TransactionSynchronization {
681800

682801
private final int order;
@@ -691,4 +810,18 @@ public int getOrder() {
691810
}
692811
}
693812

813+
static class AfterRollbackErrorHandler extends GlobalTransactionalEventErrorHandler {
814+
815+
private final ApplicationEventPublisher eventPublisher;
816+
817+
AfterRollbackErrorHandler(ApplicationEventPublisher eventPublisher) {
818+
this.eventPublisher = eventPublisher;
819+
}
820+
821+
@Override
822+
public void handle(ApplicationEvent event, @Nullable Throwable ex) {
823+
eventPublisher.publishEvent("HANDLE_ERROR");
824+
}
825+
}
826+
694827
}

0 commit comments

Comments
 (0)