@EventListener
在Spring框架下,默认被@EventListener的方法是同步的,即事件发布线程会阻塞等待所有监听器方法执行完毕后再继续执行;
而且如果发布事件的方法处在事务中,那么监听器也默认和事件发布处于同一事务中;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;@Component
public class MyEventPublisher {@Autowiredprivate ApplicationEventPublisher applicationEventPublisher;public void publishEvent() {MyEvent event = new MyEvent(this);// 发布事件applicationEventPublisher.publishEvent(event);}
}
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;@Component
public class MyEventListener {@EventListenerpublic void handleMyEvent(MyEvent event) {// 这个方法是同步执行的,会阻塞事件发布线程System.out.println("Handling event: " + event);}
}
当然,我们可以通过@Async注解使事件监听器方法异步执行,而且也顺便解决了在同一事务的问题,即事件发布和事件监听处在不同事务中
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;@Component
public class MyAsyncEventListener {@Async@EventListenerpublic void handleMyEvent(MyEvent event) {// 这个方法是异步执行的,不会阻塞事件发布线程System.out.println("Handling event asynchronously: " + event);}
}
@TransactionalEventListener
现在有一个新的需求:
1、创建一个用户
2、用户创建成功后,发送邮件给用户
可能你会很轻易的想到如下代码逻辑:
@Transactional
public User createUser(User user) {User newUser = userRepository.save(user);emailService.sendEmail(user.getEmail());return newUser;
}
但仔细想想,上述代码存在两个问题:
1、如果邮件发送失败,事务发生回滚,从而导致用户创建失败
2、邮件发送成功后,事务提交失败,导致用户收到了邮件,但账号创建失败
可能你又会想到加入事件监听器来完成解耦
/**
* 事件发布
*/
@Transactional
public Customer createCustomer(User user) {User newUser = userRepository.save(user);final UserCreatedEvent event = new UserCreatedEvent(newUser);applicationEventPublisher.publishEvent(event);return newUser;
}/**
* 事件监听
*/
@Component
public class UserCreatedEventListener {private final EmailService emailService;public UserCreatedEventListener(EmailService emailService) {this.emailService = emailService;}@EventListenerpublic void processUserCreatedEvent(UserCreatedEvent event) {emailService.sendEmail(event.getUser().getEmail());}
}
但是@EventListener 是和事件发布处于同一事务中, 其结果是和之前的代码一样;
如果加上@Async注解,也可能会出现邮件发送成功但用户创建失败的情况
如果不加事务会怎么样呢?如果事件能发布,则说明用户创建成功,那么对于邮件是否发送成功,其实对于业务来说以及不重要了,好像问题真的已经被解决!有那么一丢丢尴尬
但我还是想说另外一种解决方案(适用于有事务的情况):@TransactionalEventListener
参数phase介绍:BEFORE_COMMIT:在事务提交前执行AFTER_COMMIT:在事务提交后执行AFTER_ROLLBACK:在事务回滚后执行AFTER_COMPLETION:在事务完成时执行,这里的完成是指无论事务是成功提交还是事务回滚了参数fallbackExecution介绍:如果该事件不在事务中,且值为flase,事件将不会被处理,直接被丢弃如果该事件不在事务中,且值为true,则直接执行目标方法,效果和@EventListener一样@EventListener在底层上还是同一个事务,且是同步的,即事件监听器会阻塞事件发布线程,直到监听器方法执行完毕;当然也可以使用@Async注解使标记方法异步执行
也就是说该注解可以实现:事件发布线程事务提交之后再去执行目标方法,也就真正做到用户创建和邮件发布解耦
参考文章
【1】@TransactionalEventListener注解的使用场景