我有一个奇怪的问题,其中涉及@TransactionalEventListener
无法正确触发或由另一个@TransactionalEventListener
触发时的预期行为。
一般流程是:
这是类(节选)。
public class AccountService {
@Transactional
public User createAccount(Form registrationForm) {
// Some processing
// Persist the entity
this.accountRepository.save(userAccount);
// Publish the Event
this.applicationEventPublisher.publishEvent(new RegistrationEvent());
}
}
public class AccountEventListener {
@TransactionalEventListener
@Transactional(propagation = Propagation.REQUIRES_NEW)
public MailEvent onAccountCreated(RegistrationEvent registrationEvent) {
// Some processing
// Persist the entity
this.accountRepository.save(userAccount);
return new MailEvent();
}
}
public class MailEventListener {
private final MailService mailService;
@Async
@EventListener
public void onAccountCreated(MailEvent mailEvent) {
this.mailService.prepareAndSend(mailEvent);
}
}
此代码有效,但我的意图是在我的@TransactionalEventListener
类中使用MailEventListener
。因此,我在@EventListener
类中从@TransactionalEventListener
变为MailEventListener
的那一刻。 MailEvent不会被触发。
public class MailEventListener {
private final MailService mailService;
@Async
@TransactionalEventListener
public void onAccountCreated(MailEvent mailEvent) {
this.mailService.prepareAndSend(mailEvent);
}
}
MailEventListener
从未触发。因此,我去查看了Spring Documentation,它指出@Async @EventListener
不支持由另一个事件的返回而发布的事件。因此,我改为在ApplicationEventPublisher
类中使用AccountEventListener
。
public class AccountEventListener {
@TransactionalEventListener
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void onAccountCreated(RegistrationEvent registrationEvent) {
// Some processing
this.accountRepository.save(userAccount);
this.applicationEventPublisher.publishEvent(new MailEvent());
}
}
一旦我更改为以上内容,我的MailEventListener
现在将处理从AccountEventListener
发送的事件,但是提交表单后网页将挂起,一段时间后会引发一些异常,然后还向我发送了大约9条相同的电子邮件到我的电子邮件帐户。
我添加了一些日志记录,发现我的AccountEventListener
(this.accountRepository.save()
)在碰到异常之前实际上已经运行了9次,然后导致我的MailEventListener
执行9次,我相信,这就是为什么我在收件箱中收到9封邮件的原因。
这是Pastebin中的日志。
我不确定为什么以及导致它运行9次的原因。我的方法中没有循环或任何内容,无论是在AccountService
,AccountEventListener
还是MailEventListener
中。
谢谢!
答案 0 :(得分:6)
所以我去查看Spring文档,它指出@Async @EventListener不支持由另一个事件的返回而发布的事件。因此,我更改为在AccountEventListener类中使用ApplicationEventPublisher。
您的理解不正确。
异步侦听器不支持此功能。
这并不意味着
它声明@Async @EventListener不支持由另一个事件的返回而发布的事件。
这意味着:
此功能不支持从@Async @EventListener返回的事件。
您的设置:
@Async
@TransactionalEventListener
public void onAccountCreated(MailEvent mailEvent) {
this.mailService.prepareAndSend(mailEvent);
}
because as stated in document不起作用:
如果未在托管事务的边界内发布事件,则除非明确设置fallbackExecution()标志,否则将丢弃该事件。如果事务正在运行,则会根据其TransactionPhase处理事件。
如果使用调试,则可以看到,如果事件是从事件侦听器返回的,则该事件会在事务提交后发生,因此该事件将被丢弃。
因此,如果您按照文档中的说明设置fallbackExecution = true
,则事件将被正确侦听:
@Async
@TransactionalEventListener(fallbackExecution = true)
public void onAccountCreated(MailEvent mailEvent) {
this.mailService.prepareAndSend(mailEvent);
}
重复的行为看起来像一些重试行为,连接排队,耗尽池并引发异常。除非您提供最少的源代码来重现该问题,否则我将无法识别它。
更新
阅读代码,根本原因现在很清楚。
查看您对POST /registerPublisherCommon
的设置
MailPublisherCommonEvent
和AccountPublisherCommonEvent
是BaseEvent
的子事件createUserAccountPublisherCommon
发布类型为AccountPublisherCommonEvent
的事件MailPublisherCommonEventListener
已注册以处理MailPublisherCommonEvent
AccountPublisherCommonEventListener
以处理BaseEvent
及其所有子事件。AccountPublisherCommonEventListener
还会发布MailPublisherCommonEvent
(也是BaseEvent
)。阅读4 + 5,您将看到根本原因:AccountPublisherCommonEventListener
发布了MailPublisherCommonEvent
,它也由其自身处理,因此发生了无限事件处理。
要解决该问题,只需缩小它可以像处理的那样处理事件的类型即可。
注意
您的MailPublisherCommonEvent
设置可以工作,而与fallbackExecution
标志无关,因为您发布的是INSIDE A TRANSACTION
,而不是OUTSIDE A TRANSACTION
(通过事件监听器返回),如您的问题。
答案 1 :(得分:0)
对于它的价值,我发现了导致循环的原因以及如何解决该问题,但我仍然不明白为什么会这样发生。
如果我错了,请纠正我,设置fallbackExecution = true
并不是解决问题的真正方法。
基于Spring文档,the event is processed according to its TransactionPhase.
在我的@Transactional(propagation = Propagation.REQUIRES_NEW)
类中有AccountEventListener
本身应该是一个事务,并且MailEventListener
仅在事件中执行该阶段默认为AFTER_COMMIT
的{{1}}。
我设置了一个git来重现问题,并在这样做的同时让我发现真正出了问题的地方。话虽如此,我仍然不了解它的根本原因。
在我这样做之前,有些事情我还不确定100%,但这只是我目前的猜测/理解。
如Spring Documentation中所述,
如果未在托管事务的边界内发布事件,则除非明确设置fallbackExecution()标志,否则将丢弃该事件。如果事务正在运行,则会根据其TransactionPhase处理事件。
我猜想@TransactionalEventListener
类在使用事件作为让Spring自动发布的返回类型时未选择事件的原因是因为它发布在托管事务的边界之外。这就是为什么如果在MailEventListener
中设置(fallbackExecution = true)
的原因,它将起作用/运行,因为它是否在事务中都没有关系。
注意:上面提到的课程摘自我的第一篇文章。的 以下类的名称稍有不同,但本质上都是 还是一样,只是名字不同。
现在,回到我说过的地方,我找到了有关导致循环的原因的答案。
基本上是在侦听器中放置的参数为MailEventListener
时。
所以假设我有以下课程:
BaseEvent
还有监听器类public class BaseEvent {
private final User userAccount;
}
public class AccountPublisherCommonEvent extends BaseEvent {
public AccountPublisherCommonEvent(User userAccount) {
super(userAccount);
}
}
public class MailPublisherCommonEvent extends BaseEvent {
public MailPublisherCommonEvent(User userAccount) {
super(userAccount);
}
}
:
(Notice that the parameter is the BaseEvent)
基本上,如果侦听器的设置是这样(上面),则您进入一个循环并遇到上一个张贴者提到的异常。
重复的行为看起来像一些重试行为,连接排队,耗尽池并引发异常。
要解决该问题,只需更改输入,并定义要通过public class AccountPublisherCommonEventListener {
private final AccountRepository accountRepository;
private final ApplicationEventPublisher eventPublisher;
// Notice that the parameter is the BaseEvent
@TransactionalEventListener
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void onAccountPublisherCommonEvent(BaseEvent accountEvent) {
User userAccount = accountEvent.getUserAccount();
userAccount.setUserFirstName("common");
this.accountRepository.save(userAccount);
this.eventPublisher.publishEvent(new MailPublisherCommonEvent(userAccount));
}
}
public class MailPublisherCommonEventListener {
@Async
@TransactionalEventListener
public void onMailPublisherCommonEvent(MailPublisherCommonEvent mailEvent) {
log.info("Sending common email ...");
}
}
进行监听的类:
(Notice the addition of ({AccountPublisherCommonEvent.class}))
一种替代方法是将参数更改为实际的类名,而不是我想的public class AccountPublisherCommonEventListener {
private final AccountRepository accountRepository;
private final ApplicationEventPublisher eventPublisher;
// Notice the addition of ({AccountPublisherCommonEvent.class})
@TransactionalEventListener({AccountPublisherCommonEvent.class})
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void onAccountPublisherCommonEvent(BaseEvent accountEvent) {
User userAccount = accountEvent.getUserAccount();
userAccount.setUserFirstName("common");
this.accountRepository.save(userAccount);
this.eventPublisher.publishEvent(new MailPublisherCommonEvent(userAccount));
}
}
类。而且BaseEvent
这样做,它不再循环,也不会遇到异常。该行为将按我希望的方式运行。
如果有人将MailPublisherCommonEventListener
放置在输入中会引起循环,那么如果有人回答为什么会发生,我将不胜感激。这是Poc指向git的链接。希望我在这里有意义。
谢谢。