Spring事件机制详解

Spring注解 @EventListener 的介绍与使用示例以及异常处理 将一个方法标记为监听器,用于监听应用程序事件,事件可以是 ApplicationEvent实例,也可以是其他任意的对象。 阅读详情

一、前言

  说来惭愧,对应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:

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

}

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

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

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

public class Order {

    private String orderNo;

    private String orderStatus;

    private String goods;

    private Date createTime;
	
	//省略get、set、toString方法
}

订单创建事件OrderCreateEvent

public class OrderCreateEvent extends ApplicationEvent {

    private final Order order;

    public OrderCreateEvent(Object source,Order order) {
        super(source);
        this.order = order;
    }

    public Order getOrder(){
        return order;
    }
}

OrderService

@Service
public class OrderService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * 订单保存
     */
    public void save(){
        String orderNo = getOrderNo();
        Order order = new Order();
        order.setOrderNo(orderNo);
        order.setOrderStatus("待付款");
        order.setCreateTime(new Date());
        order.setGoods("手机");
        System.out.println("订单保存成功:" + order.toString());

        //发布事件
        applicationEventPublisher.publishEvent(new OrderCreateEvent(this,order));
        System.out.println("================");

    }

    private String getOrderNo() {
        String millis = String.valueOf(System.currentTimeMillis());
        String str = millis.substring(millis.length() - 7, millis.length() - 1);
        return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + str;
    }


    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
}

监听器OrderCreateEventListener

@Component
public class OrderCreateEventListener implements ApplicationListener<OrderCreateEvent> {


    @Override
    public void onApplicationEvent(OrderCreateEvent event) {
        System.out.printf(this.getClass().getName()+ " -- ApplicationListener 接口实现,订单号[%s]:,锁定商品[%s]\n",
                event.getOrder().getOrderNo(), event.getOrder().getGoods());
    }
}

运行测试类:

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationEventDemoApplicationTests {

	@Autowired
	OrderService orderService;

	@Test
	public void contextLoads() {
		orderService.save();
	}

}

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

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

五、注解驱动@EventListener

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

@Component
public class OrderCreateEventListenerAnnotation {

    @EventListener
    public void createOrderEvent(OrderCreateEvent event){
        System.out.println(this.getClass().getName()+"--订单创建事件,@EventListener注解实现,orderNo:"+event.getOrder().getOrderNo());
    }
}

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

六、异步事件

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

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

七、泛型支持

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

  1. 写个通用泛型类事件
/**
 * 可以自定义泛型类实现事件调度
 * Created by 2YSP on 2019/5/30.
 */
public class GenericEvent<T> {

    private  T data;
    private boolean success;

    public GenericEvent(T data,boolean success){
        this.data = data;
        this.success = success;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }
}
  1. 写个具体类型的事件
public class OrderGenericEvent extends GenericEvent<Order> {

    public OrderGenericEvent(Order data, boolean success) {
        super(data, success);
    }
}
  1. 在OrderService的save()方法中发布事件

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

  1. 事件处理
@Component
public class OrderGenericEventListener {

    @EventListener(condition = "#event.success")
    public void orderListener(GenericEvent<Order> event){
        System.out.println(this.getClass().getName()+"--处理泛型条件事件。。。");
    }
}

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

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

八、事件传播机制

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

 public void save(){
        String orderNo = getOrderNo();
        Order order = new Order();
        order.setOrderNo(orderNo);
        order.setOrderStatus("待付款");
        order.setCreateTime(new Date());
        order.setGoods("手机");
        System.out.println("订单保存成功:" + order.toString());

        //发布事件
//        applicationEventPublisher.publishEvent(new OrderCreateEvent(this,order));
        applicationEventPublisher.publishEvent(order);
//        applicationEventPublisher.publishEvent(new OrderGenericEvent(order,true));
        System.out.println("================");

    }

订单监听器

@Component
public class OrderListener {

    @EventListener
    public void orderListener(Order order){
        System.out.println(this.getClass().getName() + " -- 监听一个订单");
    }

    @EventListener
    public OrderCreateEvent orderReturnEvent(Order order){
        System.out.println(this.getClass().getName() + " -- 监听一个订单,返回一个新的事件 OrderCreateEvent");
        return new OrderCreateEvent(this,order);
    }
}

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

在这里插入图片描述
当然还可以返回多个事件,不再举例。

九、事物事件@TransactionalEventListener

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

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

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

十、总结

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

spring的Applicationcontext对事件的监听,实现类似MQ的效果 spring的Applicationcontext对事件的监听,在一个applicationContext范围内,实现类似MQ的效果 立即下载

相关推荐

[原创]Keil工程STM32F103C8T6无缝转换到STM32F103RCT6

有个项目,老版本是STM32F103C8T6,硬件资源不够了,新版本采用STM32F103RCT6。 无缝过渡,如下: 选定芯片 宏定义改为STM32F10X_HD,USE_STDPERIPH_DRIVER 删除原有的启动汇编文件,更换为高密度器件汇编文件 时钟系统紊乱? 那得看你程序怎么写: 先看F103时钟树 int main(void) { unsigned char stat...

江东风又起 2万+

事件驱动(Spring自带的@EventListener)和消息驱动(RabbitMq)

文章目录1.两者的区别:2.Spring下的事件监听的使用:2.1 发布事件2.1 定义事件源2.3 监听事件@EventListener2.4 监听事件时的事务隔离 1.两者的区别: 消息驱动和事件驱动很类似,都是先有一个事件,然后产生一个相应的消息,再把消息放入消息队列,由需要的项目获取。他们的区别是消息是谁产生的 消息驱动:鼠标管自己点击不需要和系统有过多的交互,消息由系统(第三方)循环检测,来捕获并放入消息队列。消息对于点击事件来说是被动产生的,高内聚。 事件驱动:鼠标点击产生点击事件后要向系统发送

weixin_42030357的博客 4940

ROS2机械臂实战:从基础控制到视觉抓取

本文详细介绍了ROS2机械臂从仿真环境搭建到视觉抓取的全流程实战指南。内容涵盖使用Gazebo搭建仿真环境、编写基础控制节点、集成视觉感知进行目标检测与坐标变换,并设计状态机逻辑实现自动化抓取。通过系统集成与联调,帮助开发者掌握ROS2机械臂开发的核心技能。

weixin_29197699的博客 448

spring事件监听event(默认的mq)

项目中的业务难免是相互关联的,但是代码中我们应该尽量做到低耦合,常见的做法就是引入mq来作为松耦合的一种手段;其实最常见的解耦和就是接口了,MVC直接通过接口(约定好的一种规则)相互调用,都是解耦的一种体现;还有就是springbean aop等 也就是IOC 都是解耦和的场景;通常mq是我们的选择,但是当仅仅是为了解耦的简单业务场景,其实完全没必要引入mq,而spring中的event则就可以实现解耦~

学海无涯,我用JAVA 1054

麒麟KylinV10系统yum源更换全攻略:从官方源到第三方仓库的保姆级教程

本文详细介绍了麒麟KylinV10系统yum源的更换与优化方法,包括官方源配置、第三方仓库集成及RPM包管理技巧。通过实战案例和命令示例,帮助用户快速解决软件依赖问题,提升系统管理效率。特别适合需要在国产操作系统上进行开发的工程师参考。

weixin_30752699的博客 647

开漏输出(漏极开路输出与集电极开路输出)和推挽输出怎么判断

判断一个引脚是还是。

weixin_56838517的博客 1055

Spring系列第27篇:spring事件机制详解

月底免费送书活动,这两天是最后的机会,大家尽快参与!面试官:看你是85年的我:嗯,35了面试官:那应该经验很丰富了,那我们来聊聊spring吧我:好,这块我用了10几年了,你随便问吧面试...

路人甲Java 4万+

USB之ch376s数据手册

         最近师兄交给我一个项目,虽说刚学嵌入式起步,但上手一个项目怎说都是最好的快速学习方式。因为刚了解ch376s管理U盘的数据,所以先是看了ch376s的数据手册。那我就把数据手册直接放上来吧。。。。      1、概述 CH376 是文件管理控制芯片,用于单片机系统读写 U 盘或者 SD 卡中的文件。 CH376 支持 USB 设备方式和 USB 主机方式,并且内置了 USB 通...

iBGG的博客 6003

Spring 之 事件机制详解

概念 Spring事件机制分为事件发布器(EventPublisher)、事件监听器(EventListener)和事件多播器(ApplicationEventMulticaster)。Spring事件机制对应常用设计模式之观察者模式,主要就是用来解耦。 Spring 的 ApplicationContext 提供了支持事件和代码中监听器的功能。我们可以创建 bean 用来监听在 ApplicationContext 中发布的事件。ApplicationEvent 类和在ApplicationContext

岁月安然 1514

WorkBench3.2 + VxWorks6.8安装教程

按照FTP连接主机和目标机,将整个VxWorks6.8的安装分为两个部分,一是在主机(win10系统,可以使用虚拟机或者实体机,推荐使用虚拟机)上安装workbench3.2,进行bootrom与VxWroks映像文件的生成,二是在虚拟机上安装VxWorks系统 19.10.10 首先很高兴文章能帮到大家,但由于本人已不做该方向良久,私信和评论的很多问题现在已经无法解答,如果大家有什么问题欢迎...

Alive的博客 3万+

锂电池的充电和放电曲线解读

锂电池充放电曲线是反映其性能的关键指标。充电曲线分为恒流和恒压两阶段:恒流阶段电压快速上升,恒压阶段电流逐渐降低;放电曲线则呈现平台期和快速衰减期特征。这些曲线受温度、充放电倍率及材料体系(如三元锂或磷酸铁锂)显著影响。通过分析曲线特征,可优化电池管理系统,实现精准的SOC估算、快充策略制定及故障诊断,从而在电池性能与安全性间取得平衡。不同应用场景(如新能源汽车或消费电子)对曲线形态有特定要求。

start_up_go的博客 5065

Spring及Springboot事件机制详解

Spring提供了强大的扩展体系,其中本篇介绍的事件机制是其扩展体系中一个主要分支。本篇先介绍Spring事件机制原理,再总结下spring和spring boot启动过程的主要事件,接着通过示例展示了自定义监听器的4种主要的注册方式,最后通过示例介绍了自定义事件的发布方式。

chyohn的博客 1722

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

Spring的事件机制详解

事件传播 ApplicationContext基于Observer模式(java.util包中有对应实现),提供了针对Bean的事件传播功能。通过Application. publishEvent方法,我们可以将事件通知系统内所有的ApplicationListener。事件传播的一个典型应用是,当Bean中的操作发生异常(如数据库连接失败),则通过事件传播机制通知异常监听器进行处理。在笔者的一个项目中,就曾经借助事件机制,较好的实现了当系统异常时在监视终端上报警,同时发送报警SMS至管理员手机的功能。 .

杜海的博客 902

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Spring 事件广播机制详解

写这篇文章的初衷源自对 Spring 框架中事件机制的好奇心。在编写观察者模式代码示例时,我突然想起了 Spring 框架中支持多事件监听的特性,例如ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent等等。传统的观察者模式通常是基于单一事件的,但 Spring 框架却提供了一种更为灵活的机制,可以处理多个不同类型的事件。因此,我决定深入研究 Spring 框架中多事件监听的实现机制,并将我的学习总结记录下来。

MarkusZhang的博客 1696

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Spring 事件处理机制详解,带你吃透 Spring 事件

Spring 事件处理基于 Java 观察者模式扩展。Spring 应用上下文中发布了各种事件,此外 Spring 还允许我们发送和处理自定义的事件,本篇将对 Spring 的事件机制使用及其实现进行详细介绍。

大鹏的博客 5325

Spring Boot 事件机制详解

Spring Boot 事件机制详解

嗨,欢迎来到我的 优快云 博客小天地!一名深耕多年的技术发烧友。在这里,我将把日常工作中积累的宝贵经验,从复杂架构设计的精妙之处,到代码优化的实战技巧,毫无保留地分享给大家。 7252

Spring Boot事件机制详解

事件驱动架构(Event-Driven Architecture, EDA)是一种软件设计模式,其中系统组件通过事件的发布与订阅进行通信。在Spring Boot中,事件机制为应用程序提供了松耦合的组件间通信方式,使得发布者无需关心谁在监听,监听者也无需了解事件来源

AWen_Jack的博客 1874

Spring之事件机制详解

可以看到,这个接口定义了增删查监听者的方法,所以,监听者列表的维护通过这个接口实现。需要注意的是这个接口还定义了multicastEvent方法。通过这个方法,将事件传给监听器。所以这个类,将事件和监听器,连接在一起。这里采用的是中介者模式,这个接口就是中介者角色。 ApplicationEventPublisher: Spring设计的事件发布类,我们看其源码: public interface ApplicationEventPublisher { /** Notify all matching l

m0_63176399的博客 142

Spring系列之事件机制详解

面试官:看你是85年的 我:嗯,35了 面试官:那应该经验很丰富了,那我们来聊聊spring吧 我:好,这块我用了10几年了,你随便问吧 面试官:Spring中的事件用过么? 我:用过 面试官:可以介绍一下为什么需要使用事件么? 我:使用事件的模式可以对系统进行解耦,事件源发布一个事件,事件监听器可以消费这个事件,而事件源不用关注发布的事件有哪些监听器,这可以可以对系统进行解耦 面试官:Spring事件的实现有几种方式? 我:整体来说2种方式,第一种是通过接口的方式,第二种是在方法上使用注

azhou的代码园 2422
上一篇: springboot-jpa报错No identifier specified for entity:
下一篇: 细说分布式锁
2YSP
博客等级 码龄10年 12粉丝 34原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值