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

use*_*855 10 java spring event-handling spring-boot

我有一个奇怪的问题,涉及@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这段代码有效但我的目的是@TransactionalEventListener在我的MailEventListener课堂上使用.因此,那一刻我从改变@EventListener@TransactionalEventListenerMailEventListener类.MailEvent不会被触发.

public class MailEventListener {

    private final MailService mailService;

    @Async
    @TransactionalEventListener
    public void onAccountCreated(MailEvent mailEvent) {

        this.mailService.prepareAndSend(mailEvent);
    }
}
Run Code Online (Sandbox Code Playgroud)

MailEventListener从未被触发过.所以我去查看Spring文档,它声明@Async @EventListener不支持由另一个事件的返回发布的事件.所以我改为ApplicationEventPublisherAccountEventListener课堂上使用.

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

一旦我改变了上述内容,我MailEventListener现在将接收发送的事件,AccountEventListener但是在提交表单时网页会挂起,并且在一段时间后它会抛出一些异常,然后它还向我发送了大约9封相同的电子邮件到我的电子邮件帐户.

我添加了一些日志记录,发现我的AccountEventListener(this.accountRepository.save())在遇到异常之前实际运行了9次,这导致我MailEventListener执行了9次我相信,这就是为什么我在收件箱中收到了9封邮件.

这是Pastebin中的日志.

我不确定为什么以及导致它运行9次的原因.有一个在我的方法没有环路或任何东西,无论是在AccountService,AccountEventListenerMailEventListener.

谢谢!

Mạn*_*yễn 6

所以我去查看Spring Documentation,它声明@Async @EventListener不支持由另一个事件的返回发布的事件.所以我改为在AccountEventListener类中使用ApplicationEventPublisher.

你的理解是不正确的.

该文件说:

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

这并不意味着

它声明@Async @EventListener不支持由另一个事件的返回发布的事件.

它的意思是:

此功能不支持从@Async @EventListener返回事件.

你的设置:

@Async
@TransactionalEventListener
public void onAccountCreated(MailEvent mailEvent) {

    this.mailService.prepareAndSend(mailEvent);
}
Run Code Online (Sandbox Code Playgroud)

因为如文件中所述,不起作用:

如果事件未在托管事务的边界内发布,则除非显式设置fallbackExecution()标志,否则将丢弃该事件.如果事务正在运行,则根据其TransactionPhase处理事件.

如果使用调试,则可以看到如果事件是从事件侦听器返回的,则会在事务提交后发生,因此事件将被丢弃.

因此,如果您设置fallbackExecution = true文档中所述的内容,您的事件将正确收听:

@Async
@TransactionalEventListener(fallbackExecution = true)
public void onAccountCreated(MailEvent mailEvent) {

    this.mailService.prepareAndSend(mailEvent);
}
Run Code Online (Sandbox Code Playgroud)

重复的行为看起来像一些重试行为,连接排队,耗尽池并抛出异常.除非您提供最小的源代码来重现问题,否则我无法识别它.

更新

阅读您的代码,根本原因现在很清楚.

看看你的设置 POST /registerPublisherCommon

  1. MailPublisherCommonEvent并且AccountPublisherCommonEvent是它的子事件BaseEvent
  2. createUserAccountPublisherCommon 发布类型的事件 AccountPublisherCommonEvent
  3. MailPublisherCommonEventListener 已注册处理 MailPublisherCommonEvent
  4. AccountPublisherCommonEventListener已注册处理BaseEvent所有子事件.
  5. AccountPublisherCommonEventListener也出版MailPublisherCommonEvent(也是一个BaseEvent).

阅读4 + 5,您将看到根本原因:AccountPublisherCommonEventListener发布MailPublisherCommonEvent也由其自身处理,因此发生无限事件处理.

要解决它,只需缩小它可以像你一样处理的事件类型.

注意

您的设置是为了MailPublisherCommonEvent工作而不管fallbackExecution旗帜,因为您正在发布它INSIDE A TRANSACTION,而不是OUTSIDE A TRANSACTION像您在问题中指定的那样(通过事件监听器返回).