一、前言

说来惭愧,对应Spring事件机制之前只知道实现 ApplicationListener 接口,就可以基于Spring自带的事件做一些事情(如ContextRefreshedEvent),但是最近看公司的wiki基于Spring事件的领域驱动才发现原来还有这么多东西。

二、订阅/发布(观察者模式)

2.1简介

Spring是基于事件驱动模型的,我们常用的MQ就是基于观察者模式设计的。
事件驱动模型也就是我们常说的观察者,或者发布-订阅模型;理解它的几个关键点:

  1. 首先是一种对象间的一对多的关系;最简单的如交通信号灯,信号灯是目标(一方),行人注视着信号灯(多方)。
  2. 当目标发送改变(发布),观察者(订阅者)就可以接收到改变。
  3. 观察者如何处理(如行人如何走,是快走/慢走/不走,目标不会管的),目标无需干涉;所以就松散耦合了它们之间的关系。

Java API实现和自定义实现观察者模式:

Java提供了两个接口java.util.Observable和java.util.Observer,代码可参考https://github.com/2YSP/design-pattern/tree/master/src/cn/sp/observer

三、Spring类图分析

类图

事件

  1. ApplicationEvent 继承自 JDK 的 EventObject,JDK要求所有事件将继承它,并通过source得到事件源,比如AWT事件体系也是继承它。
  2. 系统默认提供了如下ApplicationEvent事件实现:

类图

事件发布者

具体代表者是:ApplicationEventPublisher及ApplicationEventMulticaster,系统默认提供了如下实现:

ApplicationEventPublisher类图

ApplicationEventMulticaster类图
  1. ApplicationContext 接口继承了 ApplicationEventPublisher,并在 AbstractApplicationContext 实现了具体代码,实际执行是委托给ApplicationEventMulticaster(可以认为是多播)
    代码如下:

AbstractApplicationContext发布事件代码

ApplicationEventMulticaster初始化逻辑

2.根据上面代码可以看出 ApplicationContext 会自动在本地容器找一个ApplicationEventMulticaster的实现,如果没有自己new一个SimpleApplicationEventMulticaster,其中SimpleApplicationEventMulticaster发布事件的代码如下:

发布事件方法

可以看出如果给它一个executor,它就可以实现异步发布事件了,否则就是同步发送。

监听器

ApplicationListener 接口提供了onApplicationEvent方法,但是我们需要在该方法实现内部判断事件类型来处理,也没有提供按顺序触发监听器的语义,所以Spring提供了另一个接口,SmartApplicationListener:

  1. public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
  2. //如果实现支持该事件类型 那么返回true
  3. boolean supportsEventType(Class<? extends ApplicationEvent> eventType);
  4. //如果实现支持“目标”类型,那么返回true
  5. boolean supportsSourceType(Class<?> sourceType);
  6. //顺序,即监听器执行的顺序,值越小优先级越高
  7. int getOrder();
  8. }

源码分析到这里,下面说说怎么使用。

四、简单实现(实现ApplicationListener接口)

场景是我们保存一个订单后发布事件通知,以便做一些其他操作比如锁定商品。
订单实体类:

  1. public class Order {
  2. private String orderNo;
  3. private String orderStatus;
  4. private String goods;
  5. private Date createTime;
  6. //省略get、set、toString方法
  7. }

订单创建事件OrderCreateEvent

  1. public class OrderCreateEvent extends ApplicationEvent {
  2. private final Order order;
  3. public OrderCreateEvent(Object source,Order order) {
  4. super(source);
  5. this.order = order;
  6. }
  7. public Order getOrder(){
  8. return order;
  9. }
  10. }

OrderService

  1. @Service 

  2. public class OrderService implements ApplicationEventPublisherAware { 


  3. private ApplicationEventPublisher applicationEventPublisher; 


  4. /** 

  5. * 订单保存 

  6. */ 

  7. public void save(){ 

  8. String orderNo = getOrderNo(); 

  9. Order order = new Order(); 

  10. order.setOrderNo(orderNo); 

  11. order.setOrderStatus("待付款"); 

  12. order.setCreateTime(new Date()); 

  13. order.setGoods("手机"); 

  14. System.out.println("订单保存成功:" + order.toString()); 


  15. //发布事件 

  16. applicationEventPublisher.publishEvent(new OrderCreateEvent(this,order)); 

  17. System.out.println("================"); 





  18. private String getOrderNo() { 

  19. String millis = String.valueOf(System.currentTimeMillis()); 

  20. String str = millis.substring(millis.length() - 7, millis.length() - 1); 

  21. return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + str; 





  22. @Override 

  23. public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { 

  24. this.applicationEventPublisher = applicationEventPublisher; 





监听器OrderCreateEventListener

  1. @Component 

  2. public class OrderCreateEventListener implements ApplicationListener<OrderCreateEvent> { 



  3. @Override 

  4. public void onApplicationEvent(OrderCreateEvent event) { 

  5. System.out.printf(this.getClass().getName()+ " -- ApplicationListener 接口实现,订单号[%s]:,锁定商品[%s]\n", 

  6. event.getOrder().getOrderNo(), event.getOrder().getGoods()); 





运行测试类:

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class ApplicationEventDemoApplicationTests {
  4. @Autowired
  5. OrderService orderService;
  6. @Test
  7. public void contextLoads() {
  8. orderService.save();
  9. }
  10. }

控制台打印如下则表示成功实现了监听。

  1. 订单保存成功:Order{orderNo='20190601983801', orderStatus='待付款', goods='手机', createTime=Sat Jun 01 00:23:58 CST 2019}
  2. 2019-06-01 00:23:58.069 INFO 15060 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
  3. cn.sp.listener.OrderCreateEventListener -- ApplicationListener 接口实现,订单号[20190601983801]:,锁定商品[手机]
  4. ================

五、注解驱动@EventListener

接着上面的,自定义的监听器一定要实现ApplicationListener接口吗?不是,Spring还提供了注解的方式 @EventListener,使用示例如下:

  1. @Component
  2. public class OrderCreateEventListenerAnnotation {
  3. @EventListener
  4. public void createOrderEvent(OrderCreateEvent event){
  5. System.out.println(this.getClass().getName()+"--订单创建事件,@EventListener注解实现,orderNo:"+event.getOrder().getOrderNo());
  6. }
  7. }

注意:@EventListener有个condition属性,还可以支持条件判断(定义布尔SpEL表达式),只有满足条件才会触发,后面泛型支持那里有示例。

六、异步事件

上面的监听事件都是同步触发的,如果想异步的怎么办?
只需要两步:

  1. 启动类上添加 @EnableAsync注解,开启异步支持。
  2. 监听方法上添加 @Async注解
  1. @Async
  2. @EventListener
  3. public void createOrderEvent(OrderCreateEvent event){
  4. System.out.println(this.getClass().getName()+"--订单创建事件,@EventListener注解实现,orderNo:"+event.getOrder().getOrderNo());
  5. }

七、泛型支持

事件类一定要继承ApplicationEvent吗?
当然不是,我们还可以自定义泛型类实现事件调度(这个是我认为最厉害的地方了)。

  1. 写个通用泛型类事件
  1. /**
  2. * 可以自定义泛型类实现事件调度
  3. * Created by 2YSP on 2019/5/30.
  4. */
  5. public class GenericEvent<T> {
  6. private T data;
  7. private boolean success;
  8. public GenericEvent(T data,boolean success){
  9. this.data = data;
  10. this.success = success;
  11. }
  12. public T getData() {
  13. return data;
  14. }
  15. public void setData(T data) {
  16. this.data = data;
  17. }
  18. public boolean isSuccess() {
  19. return success;
  20. }
  21. public void setSuccess(boolean success) {
  22. this.success = success;
  23. }
  24. }
  1. 写个具体类型的事件
  1. public class OrderGenericEvent extends GenericEvent<Order> {
  2. public OrderGenericEvent(Order data, boolean success) {
  3. super(data, success);
  4. }
  5. }
  1. 在OrderService的save()方法中发布事件

applicationEventPublisher.publishEvent(new OrderGenericEvent(order,true));

  1. 事件处理
  1. @Component
  2. public class OrderGenericEventListener {
  3. @EventListener(condition = "#event.success")
  4. public void orderListener(GenericEvent<Order> event){
  5. System.out.println(this.getClass().getName()+"--处理泛型条件事件。。。");
  6. }
  7. }

测试结果表明,成功处理了事件。

success为true时

我们把发布事件的代码改为如下内容再测试,则不会收到事件通知。
applicationEventPublisher.publishEvent(new OrderGenericEvent(order,false));

八、事件传播机制

当我们监听一个事件处理完成时,还需要发布另一个事件,一般我们想到的是调用ApplicationEventPublisher#publishEvent发布事件方法,但Spring提供了另一种更加灵活的新的事件继续传播机制,监听方法返回一个事件,也就是方法的返回值就是一个事件对象。
示例代码:

  1. public void save(){
  2. String orderNo = getOrderNo();
  3. Order order = new Order();
  4. order.setOrderNo(orderNo);
  5. order.setOrderStatus("待付款");
  6. order.setCreateTime(new Date());
  7. order.setGoods("手机");
  8. System.out.println("订单保存成功:" + order.toString());
  9. //发布事件
  10. // applicationEventPublisher.publishEvent(new OrderCreateEvent(this,order));
  11. applicationEventPublisher.publishEvent(order);
  12. // applicationEventPublisher.publishEvent(new OrderGenericEvent(order,true));
  13. System.out.println("================");
  14. }

订单监听器

  1. @Component
  2. public class OrderListener {
  3. @EventListener
  4. public void orderListener(Order order){
  5. System.out.println(this.getClass().getName() + " -- 监听一个订单");
  6. }
  7. @EventListener
  8. public OrderCreateEvent orderReturnEvent(Order order){
  9. System.out.println(this.getClass().getName() + " -- 监听一个订单,返回一个新的事件 OrderCreateEvent");
  10. return new OrderCreateEvent(this,order);
  11. }
  12. }

启动单元测试,就会发现OrderCreateEventListener也被触发了。

enter description here

当然还可以返回多个事件,不再举例。

九、事物事件@TransactionalEventListener

从Spring 4.2开始,框架提供了一个新的@TransactionalEventListener注解,它是@EventListener的扩展,允许将事件的侦听器绑定到事务的一个阶段。绑定可以进行以下事务阶段:

  1. AFTER_COMMIT(默认的):在事务成功后触发
  2. AFTER_ROLLBACK:事务回滚时触发
  3. AFTER_COMPLETION:事务完成后触发,不论是否成功
  4. BEFORE_COMMIT:事务提交之前触发
  1. @TransactionalEventListener(phase = BEFORE_COMMIT)
  2. public void txEvent(Order order) {
  3. System.out.println("事物监听");
  4. }

上面代码的意思是仅当存在事件生成器正在运行且即将提交的事务时,才会调用此侦听器。并且,如果没有正在运行的事务,则根本不发送事件,除非我们通过将fallbackExecution 属性设置为true来覆盖它 ,即 @TransactionalEventListener(fallbackExecution = true)。

十、总结

基于事件驱动模型可以很方便的实现解耦,提高代码的可读性和可维护性,代码地址:https://github.com/2YSP/application-event-demo
疑问: 泛型支持那里如果不写一个类继承通用泛型事件,就跑不通这是为什么呢?
如果有人知道请告诉我,非常感谢。
参考:https://blog.csdn.net/sun_shaoping/article/details/84067446

Spring事件机制详解的更多相关文章

  1. Spring的事件机制详解

    同步事件和异步事件 同步事件:在一个线程里,按顺序执行业务,做完一件事再去做下一件事. 异步事件:在一个线程里,做一个事的同事,可以另起一个新的线程执行另一件事,这样两件事可以同时执行. 用一个例子来 ...

  2. Tomcat与Spring中的事件机制详解

    最近在看tomcat源码,源码中出现了大量事件消息,可以说整个tomcat的启动流程都可以通过事件派发机制串起来,研究透了tomcat的各种事件消息,基本上对tomcat的启动流程也就有了一个整体的认 ...

  3. 【移动端兼容问题研究】javascript事件机制详解(涉及移动兼容)

    前言 这篇博客有点长,如果你是高手请您读一读,能对其中的一些误点提出来,以免我误人子弟,并且帮助我提高 如果你是javascript菜鸟,建议您好好读一读,真的理解下来会有不一样的收获 在下才疏学浅, ...

  4. Spring 事务机制详解

    原文出处: 陶邦仁 Spring事务机制主要包括声明式事务和编程式事务,此处侧重讲解声明式事务,编程式事务在实际开发中得不到广泛使用,仅供学习参考. Spring声明式事务让我们从复杂的事务处理中得到 ...

  5. Spring 事务机制详解(事务的隔离性和传播性)

    原文出处: 陶邦仁 Spring事务机制主要包括声明式事务和编程式事务,此处侧重讲解声明式事务,编程式事务在实际开发中得不到广泛使用,仅供学习参考. Spring声明式事务让我们从复杂的事务处理中得到 ...

  6. spring事务管理(详解和实例)

    原文地址: 参考地址:https://blog.csdn.net/yuanlaishini2010/article/details/45792069 写这篇博客之前我首先读了<Spring in ...

  7. Android事件分发机制详解

    事件分发机制详解 一.基础知识介绍 1.经常用的事件有:MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP等 2 ...

  8. Android事件传递机制详解及最新源码分析——ViewGroup篇

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 在上一篇<Android事件传递机制详解及最新源码分析--View篇>中,详细讲解了View事件的传递机制,没掌握或者掌握不扎实的小伙伴 ...

  9. Android 的事件传递机制,详解

    Android 的事件传递机制,详解 前两天和一个朋友聊天的时候.然后说到事件传递机制.然后让我说的时候,忽然发现说的不是非常清楚,事实上Android 的事件传递机制也是知道一些,可是感觉自己知道的 ...

随机推荐

  1. 指定查询条件,查询对应的集合List(单表)

    TestDao.java(测试类) @Test public void findCollectionByConditionNoPage(){  ApplicationContext ac = new ...

  2. 找到bashrc

    (1)直接sudo gedit ~/.bashrc就可以了,编辑完后关闭就行 (2)主文件夹下ctrl+h就能找到.bashrc文件 之所以要找到bashrc文件,是为了把命令 source /opt ...

  3. Codeforces Round #369 (Div. 2) D. Directed Roads —— DFS找环 + 快速幂

    题目链接:http://codeforces.com/problemset/problem/711/D D. Directed Roads time limit per test 2 seconds ...

  4. U盘安装Ubuntu 14.04 LTS正式版 出现如下的提示,不能继续,如何操作?

    I had a problem (minor annoyance) when booting up Arch linux with a USB drive connected. The problem ...

  5. BestCoder7 1001 Little Pony and Permutation(hdu 4985) 解题报告

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4985 题目意思:有 n 个数,对于第 i 个数给出 σ(i) 的值.求出互不相交的循环的个数,并输出每 ...

  6. html5--3.5 input元素(4)

    html5--3.5 input元素(4) 学习要点 input元素及其属性 input元素 用来设置表单中的内容项,比如输入内容的文本框,按钮等 不仅可以布置在表单中,也可以在表单之外的元素使用 i ...

  7. java.lang.NoSuchMethodException: com.sun.proxy.$Proxy

    删掉了@Transactional注解,结果成功了 是这个注解造成的. 是ssh2的整合强制我们使用分层架构.

  8. bzoj2962

    线段树+卷积 这个东西直接算不太好,但是合并两段结果却很方便,假设c[i]表示选i个数乘积的和,那么$a[i]=\sum_{j=0}^{i}{b[j]*c[i-j]}$ 线段树维护即可 #includ ...

  9. bzoj3680

    $模拟退火$ $这种全局最优的问题用模拟退火$ $模拟退火就是每次向四周随机移动,移动的幅度和温度成正比,如果新的位置更优就接受,否则按一定概率接收,概率和温度成正比$ $最后稳定后再在最优解附近蹦跶 ...

  10. awk里面执行shell命令

    先把文件列表存在filename文件中 先 awk '{system("rm $0")}' filename -------WRONG 因为对于 system来说 $0 不再是某行 ...