使用Spring @TransactionalEventListener发布事件时的怪异(循环)行为

时间:2018-08-09 14:33:05

标签: java spring spring-boot event-handling

我有一个奇怪的问题,其中涉及@TransactionalEventListener无法正确触发或由另一个@TransactionalEventListener触发时的预期行为。

一般流程是:

  • AccountService将事件发布到(向AccountEventListener)
  • AccountEventListener监听事件
  • 执行一些处理,然后发布另一个事件(到MailEventListener)
  • MailEventListener侦听事件并执行一些处理

这是类(节选)。

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条相同的电子邮件到我的电子邮件帐户。

我添加了一些日志记录,发现我的AccountEventListenerthis.accountRepository.save())在碰到异常之前实际上已经运行了9次,然后导致我的MailEventListener执行9次,我相信,这就是为什么我在收件箱中收到9封邮件的原因。

这是Pastebin中的日志。

我不确定为什么以及导致它运行9次的原因。我的方法中没有循环或任何内容,无论是在AccountServiceAccountEventListener还是MailEventListener中。

谢谢!

2 个答案:

答案 0 :(得分:6)

  

所以我去查看Spring文档,它指出@Async @EventListener不支持由另一个事件的返回而发布的事件。因此,我更改为在AccountEventListener类中使用ApplicationEventPublisher。

您的理解不正确。

The document said that

  

异步侦听器不支持此功能。

这并不意味着

它声明@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的设置

  1. MailPublisherCommonEventAccountPublisherCommonEventBaseEvent的子事件
  2. createUserAccountPublisherCommon发布类型为AccountPublisherCommonEvent的事件
  3. MailPublisherCommonEventListener已注册以处理MailPublisherCommonEvent
  4. 已注册
  5. AccountPublisherCommonEventListener以处理BaseEvent及其所有子事件
  6. 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的链接。希望我在这里有意义。

谢谢。