点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

  • 原创 | Java 2021 超神之路,很肝~

  • 中文详细注释的开源项目

  • RPC 框架 Dubbo 源码解析

  • 网络应用框架 Netty 源码解析

  • 消息中间件 RocketMQ 源码解析

  • 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析

  • 作业调度中间件 Elastic-Job 源码解析

  • 分布式事务中间件 TCC-Transaction 源码解析

  • Eureka 和 Hystrix 源码解析

  • Java 并发源码

来源:geekhalo

  • 1. 领域事件

    • 1.1. 什么是领域事件

    • 1.2. 领域事件的应用场景

  • 2. Spring 对 Event 的支持

    • 2.1. 基于接口的事件处理

    • 2.2. 基于注解的事件处理

    • 2.3.基于异步事件处理

  • 4. 场景分析

    • 4.1. @EventListener

    • 4.2. @TransactionEventListener

    • 4.3. @EventListener + @Async

    • 4.4. @TransactionEventListener + @Async

  • 5. 小结


摘要: 原创出处 https://www.iocoder.cn/Fight/no-title/ 「芋道源码」欢迎转载,保留摘要,谢谢!

- [Redis 常见面试知识点小结](https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&chksm=fa496f8ecd3ee698f4954c00efb80fe955ec9198fff3ef4011e331aa37f55a6a17bc8c0335a8&scene=21&token=899450012&lang=zh_CN#wechat_redirect)
- [谈谈 Java 接口 Result 设计](https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&chksm=fa496f8ecd3ee698f4954c00efb80fe955ec9198fff3ef4011e331aa37f55a6a17bc8c0335a8&scene=21&token=899450012&lang=zh_CN#wechat_redirect)
- [小项目需要前后端分离吗?](https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&chksm=fa496f8ecd3ee698f4954c00efb80fe955ec9198fff3ef4011e331aa37f55a6a17bc8c0335a8&scene=21&token=899450012&lang=zh_CN#wechat_redirect)
- [阿里技术专家详解 DDD 系列](https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&chksm=fa496f8ecd3ee698f4954c00efb80fe955ec9198fff3ef4011e331aa37f55a6a17bc8c0335a8&scene=21&token=899450012&lang=zh_CN#wechat_redirect)
- [如何吃透一个 Java 项目?(附学习实践)](https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&chksm=fa496f8ecd3ee698f4954c00efb80fe955ec9198fff3ef4011e331aa37f55a6a17bc8c0335a8&scene=21&token=899450012&lang=zh_CN#wechat_redirect)

1. 领域事件

领域事件是 DDD 中重要的模式之一,主要用于模型或系统间的解耦,提高系统的可扩展性和可维护性。

1.1. 什么是领域事件

领域事件是领域驱动设计(Domain-Driven Design,简称DDD)中的一个重要概念,特指在领域模型中发生的有意义的事件,是对领域模型中的重要业务动作执行结果的抽象,如订单创建、支付完成等。

在DDD中,领域事件是一种用于传递信息的机制,它使得不同领域模型之间的通信变得更加简单和灵活。通过将事件分发给相关的订阅者,可以让不同的领域模型之间实现松耦合,从而更容易扩展和维护应用程序。

领域事件通常由领域对象主动触发并发布,而事件处理器则负责订阅事件并对事件进行处理。通过事件发布和订阅机制,可以在应用程序中实现高效的事件驱动架构,从而更好地支持复杂的业务逻辑和业务流程。

说起来有点抽象,简单举个例子:假设有一个电子商务系统,用户下单后需要生成订单并发送通知给相关人员。在领域模型中,可以定义一个 Order 领域对象,该对象可以包含多个属性,如订单号、下单时间、购买的商品信息、收货地址等等。当用户下单时,可以通过调用 Order 对象的方法来生成订单,同时也可以通过领域事件来发送通知。

具体来说,可以定义一个 OrderCreated 领域事件,用于表示订单创建完成的事件,该事件包含一些必要的属性,如订单号、下单时间、购买的商品信息、收货地址等等。当 Order 对象创建完成后,可以通过领域事件来触发发送通知的操作,比如发送邮件或短信通知相关人员。

1.2. 领域事件的应用场景

领域事件的应用创建众多,从图中可以看出:

领域事件可以:

  • 保证聚合间的数据一致性。当一个聚合根上的操作引发了其他聚合根的变更时,将这些变更作为领域事件发布出去,其他聚合根可以订阅这些事件并更新自己的状态,从而实现最终一致性。

  • 替换批量处理。可以作为任务的触发器,例如定时任务、异步任务,避免定时+扫描这类批量处理。

  • 实现事件源模式。将所有的领域事件全部存储下来,可以用于恢复聚合的状态,实现事件源模式;也可以用于后续的审计和调试。

  • 进行限界上下文集成。将事件从一个子域发布到另一个子域,使得这两个子域可以解耦,不用相互知道彼此的存在。

领域事件虽好,但仍需技术框架进行支持,其实 Spring 的 Event 机制就足以满足各类需求。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

2. Spring 对 Event 的支持

在 Spring 中,事件的处理可以通过三种方式来实现:

  1. 基于接口的事件处理:通过实现 ApplicationListener 接口并重写 onApplicationEvent 方法来处理事件。

  2. 基于注解的事件处理:通过在方法上添加 @EventListener 或 @TransactionEventListener 注解来处理事件,可以指定事件的类型以及监听的条件等。

  3. 基于异步事件处理:通过使用 @Async 注解来异步处理事件,可以提高应用程序的响应速度。

2.1. 基于接口的事件处理

由于与 Spring 存在强耦合,现在已经很少使用,可以直接跳过。

下面是一个基于接口的事件处理的示例代码:

@Component
public class MyEventListener implements ApplicationListener<MyEvent> {@Overridepublic void onApplicationEvent(MyEvent event) {// 处理事件System.out.println("Received event: " + event.getMessage());}
}public class MyEvent {private String message;public MyEvent(String message) {this.message = message;}public String getMessage() {return message;}
}@Component
public class MyEventPublisher {@Autowiredprivate ApplicationEventPublisher eventPublisher;public void publishEvent(String message) {MyEvent event = new MyEvent(message);eventPublisher.publishEvent(event);}
}

在这个示例中,MyEvent 是一个自定义的事件类,MyEventListener 是一个实现了 ApplicationListener 接口的监听器,用于处理 MyEvent 事件,MyEventPublisher 是用于发布事件的类。

当应用程序调用 MyEventPublisher 的 publishEvent 方法时,会触发一个 MyEvent 事件,MyEventListener 中的 onApplicationEvent 方法将被自动调用,从而处理这个事件。

2.2. 基于注解的事件处理

Spring 提供 @EventListener 和 @TransactionListener 两个注解以简化对事件的处理。

2.2.1. @EventListener

Spring 的 EventListener 监听器是一种相对于传统的事件监听方式更为简洁和灵活的事件机制。与传统的事件机制不同,EventListener 不需要显示地继承特定的事件接口,而是使用注解标识需要监听的事件类型,然后通过一个单独的监听器类处理所有类型的事件。

相比之下 EventListener 的优势主要有以下几点:

  1. 更加灵活:EventListener 不依赖于任何特定的事件接口,从而使得事件处理更加灵活,可以监听和处理任意类型的事件。

  2. 更加简洁:相比传统的事件监听方式,使用 EventListener 可以避免一系列繁琐的接口定义和实现,简化了代码结构,使得开发效率更高。

  3. 更加松耦合:EventListener 将事件发布方和事件处理方分离,遵循松耦合的设计原则,提高了代码的可维护性和扩展性。

  4. 更加可测试:由于 EventListener 可以监听和处理任意类型的事件,可以通过单元测试验证其功能是否正确,从而提高了测试的可靠性。

以下是一个简单的例子:

@Component
public class MyEventListener{@EventListenerpublic void onApplicationEvent(MyEvent event) {// 处理事件System.out.println("Received event: " + event.getMessage());}
}public class MyEvent {private String message;public MyEvent(String message) {this.message = message;}public String getMessage() {return message;}
}@Component
public class MyEventPublisher {@Autowiredprivate ApplicationEventPublisher eventPublisher;public void publishEvent(String message) {MyEvent event = new MyEvent(message);eventPublisher.publishEvent(event);}
}

相比基于接口的事件处理,EventListener 是一种更加简洁、灵活、松耦合、可测试的事件机制,能够有效地降低开发的复杂度,提高开发效率。

2.2.2. @TransactionEventListener

在 Spring 中,TransactionEventListner 和 EventListner 都是用于处理事件的接口。不同之处在于

  1. TransactionEventListner 是在事务提交后才会触发

  2. 而 EventListner 则是在事件发布后就会触发。

具体来说,在使用 Spring 的声明式事务时,可以在事务提交后触发某些事件。这就是 TransactionEventListner 的应用场景。而 EventListner 则不涉及事务,可以用于在事件发布后触发一些操作。

下面是一个简单的示例,演示了如何使用 TransactionEventListner 和 EventListner:

@Component
public class MyEventListener {@EventListenerpublic void handleMyEvent(MyEvent event) {// 处理 MyEvent}@TransactionalEventListenerpublic void handleMyTransactionalEvent(MyTransactionalEvent event) {// 处理 MyTransactionalEvent}
}@Service
public class MyService {@Autowiredprivate ApplicationEventPublisher eventPublisher;@Autowiredprivate MyRepository myRepository;@Transactionalpublic void doSomething() {// 做一些事情MyEntity entity = myRepository.findById(1L);// 发布事件eventPublisher.publishEvent(new MyEvent(this, entity));// 发布事务事件eventPublisher.publishEvent(new MyTransactionalEvent(this, entity));}
}

在这个例子中,MyEventListener 类定义了两个方法,handleMyEvent 和 handleMyTransactionalEvent,分别处理 MyEvent 和 MyTransactionalEvent 事件。其中,handleMyTransactionalEvent 方法用 @TransactionalEventListener 注解标记,表示它只会在事务提交后触发。

MyService 类中的 doSomething 方法使用 ApplicationEventPublisher 来发布事件。注意,它发布了两种不同类型的事件:MyEvent 和 MyTransactionalEvent。这两个事件会分别触发 MyEventListener 中的对应方法。

总的来说,Spring 的事件机制非常灵活,可以方便地扩展应用程序的功能。TransactionEventListner 和 EventListner 这两个接口的应用场景有所不同,可以根据实际需求选择使用。

2.3.基于异步事件处理

@Async是Spring框架中的一个注解,用于将一个方法标记为异步执行。使用该注解,Spring将自动为该方法创建一个新线程,使其在后台异步执行,不会阻塞主线程的执行。

在具体应用中,使用@Async可以大大提升应用的并发处理能力,使得系统能够更快地响应用户请求,提高系统的吞吐量。

@Async 和 @EventListener 或 @TransactionEventListener 注解在一起使用时,会产生异步的事件处理器。使用这种组合的方式,事件处理器会在单独的线程池中执行,以避免阻塞主线程。这种方式在需要处理大量事件或者事件处理器耗时较长的情况下非常有用,可以有效提高应用的性能和可伸缩性。同时,Spring 框架对这种方式也提供了完善的支持,可以方便地使用这种方式来实现异步事件处理。

下面是一个简单的示例代码,演示了如何在 Spring 中使用 @Async 和 @EventListener 一起实现异步事件处理:

@Component
public class ExampleEventListener {@Async@EventListenerpublic void handleExampleEvent(ExampleEvent event) {// 在新的线程中执行异步逻辑// ...}
}

在这个示例中,ExampleEventListener 类中的 handleExampleEvent 方法使用了 @Async 和 @EventListener 注解,表示这个方法是一个异步事件监听器。当一个 ExampleEvent 事件被触发时,这个方法会被异步地执行。在这个方法中,可以执行任何异步的逻辑处理,比如向队列发送消息、调用其他服务等。

备注:在使用 @Async 时,需要根据业务场景对线程池进行自定义,以免出现资源不够的情况(Spring 默认使用单线程处理@Async异步任务)

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

4. 场景分析

综上所述,当领域事件发出来之后,不同的注解会产生不同的行为,简单汇总如下:

@EventListener @TransactionEventListener
无 @Async 顺序、同步执行 事务提交后、同步执行
有 @Async 顺序、异步执行 事务提交后、异步执行

4.1. @EventListener

特点:

  1. 顺序执行。调用 publish(Event) 后,自动触发对 @EventListner 注释方法的调用

  2. 同步执行。使用主线程执行,方法抛出异常会中断调用链路,会触发事务的回归

应用场景:

  1. 事务消息表。在同一事务中完成对业务数据和消息表的修改

  2. 业务验证。对业务对象进行最后一次验证,如果验证不通过直接抛出异常中断数据库事务

  3. 业务插件。在当前线程和事务中执行插件完成业务扩展

4.2. @TransactionEventListener

特点:

  1. 事务提交后执行。调用 publish(Event) 时,只是向上下文中注册了一个回调器,并不会立即执行;只有在事务提交后,才会触发对 @TransactionEventListner 注释方法的调用

  2. 同步执行。使用主线程执行,方法抛出异常会中断调用链路,当不会回归事务(事务已提交,没有办法进行回归)

应用场景:

  1. 数据同步。事务提交后,将变更同步到 ES 或 Cache

  2. 记录审计日志。只有在业务变更成功更新到数据库时才进行记录

备注:@TransactionEventLisnter 必须在事务上下文中,脱离上下文,调用不会生效

4.3. @EventListener + @Async

特点:

  1. 顺序执行。调用 publish(Event) 后,自动触发对 @EventListner 注释方法的调用

  2. 异步执行。使用独立的线程池执行任务,方法抛出异常对主流程没有任何影响

应用场景:

  1. 记日志明细日志,辅助排查问题

4.4. @TransactionEventListener + @Async

特点:

  1. 事务提交后执行。调用 publish(Event) 时,只是向上下文中注册了一个回调器,并不会立即执行;只有在事务提交后,才会触发对 @TransactionEventListner 注释方法的调用

  2. 异步执行。使用独立的线程池执行任务,方法抛出异常对主流程没有任何影响

应用场景:异步处理。记录操作日志,异步保存数据等 备注:@TransactionEventLisnter 必须在事务上下文中,脱离上下文,调用不会生效

5. 小结

领域事件的落地,不仅需要强大的设计能力,还需要与之匹配的基础设施。Spring 作为最常用的框架,基于发布订阅实现了完整的一套 Event 管理机制。工具在手是否能根据业务场景选择合适的解决方案就成了研发的职责,简单思考以下组合适用场景是什么:

  1. @EventListener

  2. @TransactionEventListener

  3. @EventListener + @Async

  4. @TransactionEventListener + @Async



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)

Spring Event + DDD = 王炸!!相关推荐

  1. 王炸!无需额外数据,Transformers超越CNN问鼎ImageNet

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 仅作学术分享,不代表本公众号立场,侵权联系删除 转载于:reddit ...

  2. HPE品牌存储为啥高调? 因为“王炸”多啊!

    作为紫光华山的两个品牌,HPE.H3C推广有条不紊的进行着.不久前全新H3C品牌存储产品亮相,而同样HPE品牌存储产品更是新品不断.这么看来给存储业界的感觉就是,看似平静的存储市场,好像有一只庞然大物 ...

  3. 共享单车哈罗王炸连出,OFO小心沦为炮灰

    在共享单车行业寒冬之际,哈罗单车却是好消息不断,先是12月4日宣布获得3.5亿美元的D1轮融资,昨天又再次宣布完成10亿元的D2轮融资,在这大家都勒紧裤腰带过日子的寒冬里,哈罗单车可谓是王炸连出,来了 ...

  4. 王炸养成记——看Linux 25周年发展变化

    即使桌面端成为Linux不可挽回的"朱砂痣",但是也丝毫无损Linux从一文不名成长为王炸的好牌. 是否有人还记得,1976年比尔盖茨那封义愤填膺的<写给电脑爱好者的公开信& ...

  5. EventBus VS Spring Event

    EventBus VS Spring Event 本地异步处理,采用事件机制 可以使 代码解耦,更易读.事件机制实现模式是 观察者模式(或发布订阅模式),主要分为三部分:发布者.监听者.事件. Gua ...

  6. Spring event 使用完全指南

    说明 此篇文章以 Spring 4.2+ 为例,在此版本之前略微有不同. 笔者自2014年起开始接触和使用 Spring event,根据 Spring event 的原理开发了 JFinal-eve ...

  7. 找找 Spring Event 源码中各种设计模式的使用

    为什么80%的码农都做不了架构师?>>>    本文将按照Spring Event 是什么鬼的思路寻找 Spring 源码中与 Spring Event 有关的设计模式实现 初始化- ...

  8. 5天让你技能加满的“王炸组合”,速来!

    简介:<实时数仓入门训练营>,理论与实践的摩擦,概念与案例的碰撞,从 0 到1 快速上手,让自己技能加点,速来报名! 随着数字化业务的扩张,企业的数据量呈现爆发式增长,作为 "数 ...

  9. 苹果出5g手机吗_华为打响5G手机第一枪,苹果却扔出620亿“王炸”,任正非:榜样...

    Hello,大家好,欢迎收看本期科技资讯! 大家都知道,自华为.小米等国产手机崛起之后,美国手机巨头苹果在中国市场接连失利,已经连续多个季度出现销量下滑的情况了,加上与高通的"爱恨情仇&qu ...

最新文章

  1. web设计经验七13步打造优雅的WEB字体
  2. CopyOnWriteArrayList简介
  3. 案例代码:sprimngboot备份数据库
  4. sqlite sqlite3_bind_int sqlite3_bind_text
  5. spring boot——MockMvc的用法
  6. 字节跳动想取消大下周,遭到部分员工激烈反对
  7. vertical-align属性探究
  8. shell 相关知识(1)
  9. 求多个数最小公倍数和最大公约数
  10. Linux系统调用过程(Linux0.11内核实验)
  11. ConvMF论文解读
  12. electron打包exe文件
  13. mysql文件后缀名是什么_数据库文件的扩展名是什么?
  14. 小米怎么快速回到顶部_拆解报告:小米小爱鼠标采用炬芯ATB110X蓝牙物联网方案 -...
  15. 贪婪的大脑:为何人类会无止境地寻求意义 目录
  16. IDEA快捷键CTRL+ALT+L格式化失效
  17. CANoe CAPL 接收以太网报文--UDP
  18. unity使ui面向镜头_pihqcam面向相机的软件ui
  19. DL4J源码阅读(一):网络初始化
  20. 一段很有意思小代码:视频中提取心率

热门文章

  1. Oracle数据库REMOTE_LOGIN_PASSWORDFILE参数的设置
  2. kde函数matlab,KED Toolbox for Matlab 使用
  3. 聚焦云计算、大数据、人工智能等开源技术,这场开源开发者的盛会不容错过!
  4. 安卓桌面软件_安卓和苹果不同系统的手机怎么互传便签内容?
  5. 移除readonly时间js
  6. iphone外屏碎了多少钱_苹果手机屏幕碎了换个外屏多少钱
  7. Python下获取视频的旋转角度信息
  8. 玩客云实名认证安全吗_拼多多实名认证安全吗
  9. 一周造一个新平台,猩便利的程序猿到底经历了什么?
  10. 医学影像检测方法(B超、DR、CT、MRI)