SpringBoot事件发布与监听超详细讲解
作者:喜羊羊sk
应用场景
比如: 我们现在的应用是Maven聚合工程的多模块方式开发。现在有biz模块、bill模块、pay模块。biz模块有一个用户注册功能,bill模块有一个发生邮件的功能,每次用户注册都需要调用bill模块的发送邮件功能。我们只需要在pom引入bill模块的依赖,注入bill模块发送邮件的service接口,即可以完成操作。
现在业务有调整,pay模块也需要完成支付业务发送邮件,也需要和上面操作一样,操作会很费时费力。有没有一种办法可以把它们解耦出来?
答案是有的,我们可以使用事件的方式,biz模块只要注册成功,发布一个用户注册成功的事件,pay模块支付成功,发布一个支付成功的事件。让bill模块监听了此事件的去做剩下的发送邮件事件就好了。对于事件发布者而言,不需要关心谁监听了该事件,以此来解耦业务。注意:必须在同一个Spring容器内,不能完成跨容器的通信,跨容器的通信可以考虑中间件消息队列
Spring的事件应该是在3.x版本就发布的功能了,并越来越完善,其为bean和bean之间的消息通信提供了支持。
概述
ApplicationEvent以及Listener是Spring为我们提供的一个事件监听、订阅的实现,内部实现原理是观察者设计模式,设计初衷也是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。
- ApplicationEvent就是Spring的事件接口
- ApplicationListener就是Spring的事件监听器接口,所有的监听器都实现该接口
- ApplicationEventPublisher是Spring的事件发布接口,ApplicationContext实现了该接口
其执行的流程大致为:
当一个事件源产生事件时,它通过事件发布器ApplicationEventPublisher发布事件,然后事件广播器ApplicationEventMulticaster会去事件注册表ApplicationContext中找到事件监听器ApplicationListnener,并且逐个执行监听器的onApplicationEvent方法,从而完成事件监听器的逻辑。
自定义事件发布和监听
pom
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
自定义事件源和实体
Spring中,事件源不强迫继承ApplicationEvent
接口的,也就是可以直接发布任意一个对象类(实体类,Map,List,String等任意对象类)。但内部其实是使用PayloadApplicationEvent类进行包装了一层。
@TableName("user") @Data public class User { @TableId(type = IdType.ASSIGN_ID,value = "cate_id") private String cateId; @TableField("cate_code") private String cateCode; @TableField("user_name") private String username; @TableField("age") private Integer age; @TableField("sex") private String sex; } public class SendEmailEvent extends ApplicationEvent { private User user; public SendEmailEvent(Object source, User user) { super(source); this.user = user; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public String toString() { return "SendEmailEvent{" + "user=" + user + '}'; } }
发布事件
这里我手动处理的事务,也可以使用@Transactional
处理。这里我主要模拟发布事件发生异常和监听事件中发生异常的情况。
默认情况下,发布事件和监听事件都是在一个线程中同步执行的。业务发布事件类中,当try代码块int a = 1 / 0;
模拟保存用户异常,会进入到catch块中回滚保存事务,不会发布事件。
当业务类保存用户,并执行事件发布逻辑成功,使用@EventListener
的监听类中发生异常,也会到发布事件的catch块中回滚保存的事务。即证明了发布事件和监听事件都是在一个线程中同步执行的。事务是一致性的。
@Service @Slf4j //@Transactional public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Resource private ApplicationEventPublisher eventPublisher; @Resource private DataSourceTransactionManager transactionManager; @Override public Boolean saveUser(User user) { // 手动开启事务 start DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus transaction = transactionManager.getTransaction(definition); // 手动开启事务 end log.info("[UserService] start insert saveUser"); // 保存用户 Boolean result = false; try { if (Objects.nonNull(user)) { // 保存用户 result = this.save(user); // 模拟异常回滚 // int a = 1 / 0; } // 1、发布ApplicationEvent对象 eventPublisher.publishEvent(new SendEmailEvent(this,user)); // 2、发送邮件 (发布任意一个对象类) eventPublisher.publishEvent(user.getUsername()); log.info("[UserService] finish insert saveUser"); // 手动提交事务 end transactionManager.commit(transaction); // 手动提交事务 start } // 异常回滚 catch (Exception e) { log.error("saveUser异常 fail:{}", e); // 手动回滚事务 start transactionManager.rollback(transaction); // 手动回滚事务 end } return result; } }
监听类使用ApplicationListener方式
@Component @Slf4j public class CustomListener implements ApplicationListener<SendEmailEvent> { @Override public void onApplicationEvent(SendEmailEvent event) { send(event.getUser().getUsername()); } public void send(String username) { String code = UUID.randomUUID().toString().replace("_", "").substring(0, 4); log.info("生成验证码: {}",code); // 可能由于网络带宽,发送验证码异常,模拟监听事件异常 int i = 1/0; log.info("【{}】本次登陆验证码为:{},请在5分钟内完成验证,请勿将验证码泄露给他人。",username,code); } }
监听类使用@EventListener方式
使用@EventListener的condition可以实现更加精细的事件监听,condition支持SpEL表达式,可根据事件源的参数来判断是否监听。事件源为对象,可以使用对象.属性。如: @EventListener(condition = "#customEvent.billType=='pay'")
@EventListener(condition = "#username.equals('风雨')") public void sendEmail(String username) { log.info("transactionEventListener start"); // 发生验证码 send(username); log.info("transactionEventListener finish"); } public void send(String username) { String code = UUID.randomUUID().toString().replace("_", "").substring(0, 4); log.info("生成验证码: {}",code); // 可能由于网络带宽,发送验证码异常,模拟监听事件异常 int i = 1/0; log.info("【{}】本次登陆验证码为:{},请在5分钟内完成验证,请勿将验证码泄露给他人。",username,code); }
Spring事件最佳实践
通用泛型类事件
事件类可以不继承ApplicationEvent类,定义通用泛型类事件对象,其他事件对象继承该对象
@Data @AllArgsConstructor @NoArgsConstructor public class GenericEvent<T> { /** * 业务类型 */ private String billType; private T data; }
发送银行成功事件
public class SendBankSuccessEvent extends GenericEvent<SendBank>{ public SendBankSuccessEvent(String billType, SendBank data) { super(billType, data); } }
发送银行失败事件
public class SendBankFailEvent extends GenericEvent<SendBank>{ public SendBankFailEvent(String billType, SendBank data) { super(billType, data); } }
发送银行退回事件
public class SendBankGoBackEvent extends GenericEvent<Map>{ public SendBankGoBackEvent(String billType, Map data) { super(billType, data); } }
@Data public class SendBank { private String username; private String paymentAccount; private String money; }
发布事件类
发送事件类一般可以直接是Service层的实现类
@Service public class SendBankServiceImpl { @Resource private ApplicationContext applicationContext; public void sendBank(String event, String billType) { SendBank sendBank = new SendBank(); sendBank.setUsername("风雨修"); sendBank.setPaymentAccount("中国建设银行"); sendBank.setMoney("2000"); if ("success".equals(event)) { if ("DYZC".equals(billType)) { applicationContext.publishEvent(new SendBankSuccessEvent("DYZC", sendBank)); } if ("ZYSL".equals(billType)) { applicationContext.publishEvent(new SendBankSuccessEvent("ZYSL", sendBank)); } if ("SLHZ".equals(billType)) { applicationContext.publishEvent(new SendBankSuccessEvent("SLHZ", sendBank)); } } else if ("fail".equals(event)){ applicationContext.publishEvent(new SendBankFailEvent("ZYSL", sendBank)); } else if ("goBack".equals(event)){ HashMap<Object, Object> map = Maps.newHashMap(); map.put("username", "喜羊羊sk"); map.put("age", 23); map.put("sex","男"); map.put("hobby","hello world"); applicationContext.publishEvent(new SendBankGoBackEvent("SLHZ", map)); } } }
事件监听类
@EventListener注解属性
- classes: 此侦 听器处理的事件类。
- condition:用于使事件处理成为条件的SpEL表达式,如果表达式的计算结果为布尔值true,则将处理该事件
一个事件可以有很多个监听者,可以通过classes属性指定具体监听事件类,通过condition可以指定事件类的属性值满足条件才生效执行
@Component @Slf4j public class GenericEventListener { /** * 同一种事件,不同业务类型。实现不同的监听处理 * 通过 @EventListener注解属性 * classes: 此 侦听器处理的事件类。 * condition:用于使事件处理成为条件的SpEL表达式,如果表达式的计算结果为布尔值true,则将处理该事件 */ @EventListener(classes = SendBankSuccessEvent.class,condition = "#event.billType.equalsIgnoreCase('dyzc')") public void DYZCsendBankSuccessListener(GenericEvent<SendBank> event) { log.info("event: {}", event); log.info("data: {}", event.getData()); } @EventListener(classes = SendBankSuccessEvent.class,condition = "#event.billType.equalsIgnoreCase('zysl')") public void ZYSLsendBankSuccessListener(GenericEvent<SendBank> event) { log.info("event: {}", event); log.info("data: {}", event.getData()); } @EventListener(classes = SendBankSuccessEvent.class,condition = "#event.billType.equalsIgnoreCase('slhz')") public void SLHZsendBankSuccessListener(GenericEvent<SendBank> event) { log.info("event: {}", event); log.info("data: {}", event.getData()); } /** * end * */ @EventListener(classes = SendBankFailEvent.class) public void sendBankFailListener(GenericEvent<SendBank> event) { log.info("event: {}", event); log.info("data: {}", event.getData()); } @EventListener(classes = SendBankGoBackEvent.class) public void sendGoBackListener(GenericEvent<Map> event) { log.info("event: {}", event); log.info("data: {}", event.getData()); } }
异步监听处理
默认情况下,发布事件和监听事件都是同步执行,在一个线程中的。在需要异步处理时,可以在方法上加上@Async进行异步化操作。此时,可以定义一个线程池,同时开启异步功能,加入@EnableAsync。
@Async @EventListener public void sendEmail(String username) { log.info("transactionEventListener start"); // 发生验证码 send(username); log.info("transactionEventListener finish"); }
事务绑定事件
当一些场景下,比如在用户注册成功后,即数据库事务提交了,之后再异步发送邮件等,不然会发生数据库插入失败,但事件却发布了,也就是邮件发送成功了的情况。此时,我们可以使用@TransactionalEventListener
注解或者TransactionSynchronizationManager
类来解决此类问题,也就是:事务成功提交后,再执行事件。
TransactionSynchronizationManager类
@EventListener public void afterRegisterSendMail(SendEmailEvent event) { // Spring 4.2 之前 TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronizationAdapter() { @Override public void afterCommit() { send(username); } } ); }
上面的代码将在事务提交后执行.如果在非事务context中将抛出java.lang.IllegalStateException: Transaction synchronization is not active
@EventListener public void afterRegisterSendMail(SendEmailEvent event) { // Spring 4.2 之前 if (TransactionSynchronizationManager.isActualTransactionActive()) { TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronizationAdapter() { @Override public void afterCommit() { send(event); } }); } else { send(username); } }
这样无论是否有事务都能兼容啦.
@TransactionalEventListener
Spring 4.2除了EventListener之外,额外提供了新的注解@TransactionalEventListener
@TransactionalEventListener public void sendEmail(String username) { log.info("transactionEventListener start"); // 发生验证码 send(username); log.info("transactionEventListener finish"); }
这个注解的强大之处在于可一直控制事务的 before/after commit, after rollback ,after completion (commit或 rollback)
. 默认情况下,在事务中的Event将会被执行,
到此这篇关于SpringBoot事件发布与监听超详细讲解的文章就介绍到这了,更多相关SpringBoot事件发布与监听内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!