当前位置:首页 > 科技  > 软件

使用上 Spring 的事件机制,真香!

来源: 责编: 时间:2023-09-18 21:41:51 214观看
导读本文主要是简单的讲述了Spring的事件机制,基本概念,讲述了事件机制的三要素事件、事件发布、事件监听器。如何实现一个事件机制,应用的场景,搭配@Async注解实现异步的操作等等。希望对大家有所帮助。Spring的事件机制的基

本文主要是简单的讲述了Spring的事件机制,基本概念,讲述了事件机制的三要素事件、事件发布、事件监听器。如何实现一个事件机制,应用的场景,搭配@Async注解实现异步的操作等等。希望对大家有所帮助。oQ028资讯网——每日最新资讯28at.com

oQ028资讯网——每日最新资讯28at.com

Spring的事件机制的基本概念

Spring的事件机制是Spring框架中的一个重要特性,基于观察者模式实现,它可以实现应用程序中的解耦,提高代码的可维护性和可扩展性。Spring的事件机制包括事件、事件发布、事件监听器等几个基本概念。其中,事件是一个抽象的概念,它代表着应用程序中的某个动作或状态的发生。事件发布是事件发生的地方,它负责产生事件并通知事件监听器。事件监听器是事件的接收者,它负责处理事件并执行相应的操作。在Spring的事件机制中,事件源和事件监听器之间通过事件进行通信,从而实现了模块之间的解耦。oQ028资讯网——每日最新资讯28at.com

举个例子:用户修改密码,修改完密码后需要短信通知用户,记录关键性日志,等等其他业务操作。oQ028资讯网——每日最新资讯28at.com

如下图,就是我们需要调用多个服务来进行实现一个修改密码的功能。oQ028资讯网——每日最新资讯28at.com

oQ028资讯网——每日最新资讯28at.com

 使用了事件机制后,我们只需要发布一个事件,无需关心其扩展的逻辑,让我们的事件监听器去处理,从而实现了模块之间的解耦。oQ028资讯网——每日最新资讯28at.com

oQ028资讯网——每日最新资讯28at.com

 事件

通过继承ApplicationEvent,实现自定义事件。是对 Java EventObject 的扩展,表示 Spring 的事件,Spring 中的所有事件都要基于其进行扩展。其源码如下。oQ028资讯网——每日最新资讯28at.com

我们可以获取到timestamp属性指的是发生时间。oQ028资讯网——每日最新资讯28at.com

oQ028资讯网——每日最新资讯28at.com

 事件发布

oQ028资讯网——每日最新资讯28at.com

事件发布是事件发生的地方,它负责产生事件并通知事件监听器。ApplicationEventPublisher用于用于发布 ApplicationEvent 事件,发布后 ApplicationListener 才能监听到事件进行处理。源码如下。oQ028资讯网——每日最新资讯28at.com

需要一个ApplicationEvent,就是我们的事件,来进行发布事件。oQ028资讯网——每日最新资讯28at.com

oQ028资讯网——每日最新资讯28at.com

 事件监听器

ApplicationListener 是 Spring 事件的监听器,用来接受事件,所有的监听器都必须实现该接口。该接口源码如下。oQ028资讯网——每日最新资讯28at.com

oQ028资讯网——每日最新资讯28at.com

 Spring的事件机制的使用方法

oQ028资讯网——每日最新资讯28at.com

下面会给大家演示如何去使用Spring的事件机制。就拿修改密码作为演示。oQ028资讯网——每日最新资讯28at.com

oQ028资讯网——每日最新资讯28at.com

如何定义一个事件

新增一个类,继承我们的ApplicationEvent。oQ028资讯网——每日最新资讯28at.com

如下面代码,继承后定义了一个userId,有一个UserChangePasswordEvent方法。这里就定义我们监听器需要的业务参数,监听器需要那些参数,我们这里就定义那些参数。oQ028资讯网——每日最新资讯28at.com

/** * @Author JiaQIng * @Description 修改密码事件 * @ClassName UserChangePasswordEvent * @Date 2023/3/26 13:55 **/@Getter@Setterpublic class UserChangePasswordEvent extends ApplicationEvent {    private String userId;    public UserChangePasswordEvent(String userId) {        super(new Object());        this.userId = userId;    }}

oQ028资讯网——每日最新资讯28at.com

如何监听事件

实现监听器有两种方法:oQ028资讯网——每日最新资讯28at.com

(1) 新建一个类实现ApplicationListener接口,并且重写onApplicationEvent方法注入到Spring容器中,交给Spring管理如下代码新建了一个发送短信监听器,收到事件后执行业务操作****;oQ028资讯网——每日最新资讯28at.com

/** * @Author JiaQIng * @Description 发送短信监听器 * @ClassName MessageListener * @Date 2023/3/26 14:16 **/@Componentpublic class MessageListener implements ApplicationListener<UserChangePasswordEvent> {    @Override    public void onApplicationEvent(UserChangePasswordEvent event) {        System.out.println("收到事件:" + event);        System.out.println("开始执行业务操作给用户发送短信。用户userId为:" + event.getUserId());    }}

(2) 使用@EventListener注解标注处理事件的方法,此时Spring将创建一个ApplicationListenerbean对象,使用给定的方法处理事件源码如下参数可以给指定的事件这里巧妙的用到了@AliasFor的能力,放到了@EventListener身上注意:一般建议都需要指定此值,否则默认可以处理所有类型的事件,范围太广了;oQ028资讯网——每日最新资讯28at.com

oQ028资讯网——每日最新资讯28at.com

 代码如下。新建一个事件监听器,注入到Spring容器中,交给Spring管理。在指定方法上添加@EventListener参数为监听的事件。方法为业务代码。使用 @EventListener 注解的好处是一个类可以写很多监听器,定向监听不同的事件,或者同一个事件。oQ028资讯网——每日最新资讯28at.com

/** * @Author JiaQIng * @Description 事件监听器 * @ClassName LogListener * @Date 2023/3/26 14:22 **/@Componentpublic class ListenerEvent {    @EventListener({ UserChangePasswordEvent.class })    public void LogListener(UserChangePasswordEvent event) {        System.out.println("收到事件:" + event);        System.out.println("开始执行业务操作生成关键日志。用户userId为:" + event.getUserId());    }    @EventListener({ UserChangePasswordEvent.class })    public void messageListener(UserChangePasswordEvent event) {        System.out.println("收到事件:" + event);        System.out.println("开始执行业务操作给用户发送短信。用户userId为:" + event.getUserId());    }}

(3)  @TransactionalEventListener来定义一个监听器,他与@EventListener不同的就是@EventListener标记一个方法作为监听器,他默认是同步执行,如果发布事件的方法处于事务中,那么事务会在监听器方法执行完毕之后才提交事件发布之后就由监听器去处理,而不要影响原有的事务,也就是说希望事务及时提交我们就可以使用该注解来标识注意此注解需要spring-tx的依赖;oQ028资讯网——每日最新资讯28at.com

注解源码如下:主要是看一下注释内容。oQ028资讯网——每日最新资讯28at.com

// 在这个注解上面有一个注解:@EventListener,所以表明其实这个注解也是个事件监听器。 @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@EventListenerpublic @interface TransactionalEventListener { /**  * 这个注解取值有:BEFORE_COMMIT(指定目标方法在事务commit之前执行)、AFTER_COMMIT(指定目标方法在事务commit之后执行)、  * AFTER_ROLLBACK(指定目标方法在事务rollback之后执行)、AFTER_COMPLETION(指定目标方法在事务完成时执行,这里的完成是指无论事务是成功提交还是事务回滚了)  * 各个值都代表什么意思表达什么功能,非常清晰,  * 需要注意的是:AFTER_COMMIT + AFTER_COMPLETION是可以同时生效的  * AFTER_ROLLBACK + AFTER_COMPLETION是可以同时生效的  */ TransactionPhase phase() default TransactionPhase.AFTER_COMMIT; /**  * 表明若没有事务的时候,对应的event是否需要执行,默认值为false表示,没事务就不执行了。  */ boolean fallbackExecution() default false; /**  *  这里巧妙的用到了@AliasFor的能力,放到了@EventListener身上  *  注意:一般建议都需要指定此值,否则默认可以处理所有类型的事件,范围太广了。  */ @AliasFor(annotation = EventListener.class, attribute = "classes") Class<?>[] value() default {}; /**  * The event classes that this listener handles.  * <p>If this attribute is specified with a single value, the annotated  * method may optionally accept a single parameter. However, if this  * attribute is specified with multiple values, the annotated method  * must <em>not</em> declare any parameters.  */ @AliasFor(annotation = EventListener.class, attribute = "classes") Class<?>[] classes() default {}; /**  * Spring Expression Language (SpEL) attribute used for making the event  * handling conditional.  * <p>The default is {@code ""}, meaning the event is always handled.  * @see EventListener#condition  */ @AliasFor(annotation = EventListener.class, attribute = "condition") String condition() default ""; /**  * An optional identifier for the listener, defaulting to the fully-qualified  * signature of the declaring method (e.g. "mypackage.MyClass.myMethod()").  * @since 5.3  * @see EventListener#id  * @see TransactionalApplicationListener#getListenerId()  */ @AliasFor(annotation = EventListener.class, attribute = "id") String id() default "";}

使用方式如下。phase事务类型,value指定事件。oQ028资讯网——每日最新资讯28at.com

/** * @Author JiaQIng * @Description 事件监听器 * @ClassName LogListener * @Date 2023/3/26 14:22 **/@Componentpublic class ListenerEvent {    @EventListener({ UserChangePasswordEvent.class })    public void logListener(UserChangePasswordEvent event) {        System.out.println("收到事件:" + event);        System.out.println("开始执行业务操作生成关键日志。用户userId为:" + event.getUserId());    }    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT,value = { UserChangePasswordEvent.class })    public void messageListener(UserChangePasswordEvent event) {        System.out.println("收到事件:" + event);        System.out.println("开始执行业务操作给用户发送短信。用户userId为:" + event.getUserId());    }}

oQ028资讯网——每日最新资讯28at.com

如何发布一个事件

(1) 使用ApplicationContext进行发布,由于ApplicationContext已经继承了ApplicationEventPublisher,因此可以直接使用发布事件源码如下;oQ028资讯网——每日最新资讯28at.com

oQ028资讯网——每日最新资讯28at.com

 (2) 直接注入我们的ApplicationEventPublisher,使用@Autowired注入一下;oQ028资讯网——每日最新资讯28at.com

三种发布事件的方法,我给大家演示一下@Autowired注入的方式发布我们的事件。oQ028资讯网——每日最新资讯28at.com

@SpringBootTestclass SpirngEventApplicationTests {    @Autowired    ApplicationEventPublisher appEventPublisher;    @Test    void contextLoads() {        appEventPublisher.publishEvent(new UserChangePasswordEvent("1111111"));    }}

我们执行一下看一下接口:oQ028资讯网——每日最新资讯28at.com

oQ028资讯网——每日最新资讯28at.com

 测试成功。oQ028资讯网——每日最新资讯28at.com

oQ028资讯网——每日最新资讯28at.com

搭配@Async注解实现异步操作

监听器默认是同步执行的,如果我们想实现异步执行,可以搭配@Async注解使用,但是前提条件是你真的懂@Async注解,使用不当会出现问题的。 后续我会出一篇有关@Async注解使用的文章。这里就不给大家详细的解释了。有想了解的同学可以去网上学习一下有关@Async注解使用。oQ028资讯网——每日最新资讯28at.com

使用@Async时,需要配置线程池,否则用的还是默认的线程池也就是主线程池,线程池使用不当会浪费资源,严重的会出现OOM事故。oQ028资讯网——每日最新资讯28at.com

下图是阿里巴巴开发手册的强制要求。oQ028资讯网——每日最新资讯28at.com

oQ028资讯网——每日最新资讯28at.com

 简单的演示一下:这里声明一下俺没有使用线程池,只是简单的演示一下。oQ028资讯网——每日最新资讯28at.com

(1) 在我们的启动类上添加@EnableAsync开启异步执行配置;oQ028资讯网——每日最新资讯28at.com

@EnableAsync@SpringBootApplicationpublic class SpirngEventApplication {    public static void main(String[] args) {        SpringApplication.run(SpirngEventApplication.class, args);    }}

(2) 在我们想要异步执行的监听器上添加@Async注解:oQ028资讯网——每日最新资讯28at.com

/** * @Author JiaQIng * @Description 事件监听器 * @ClassName LogListener * @Date 2023/3/26 14:22 **/@Componentpublic class ListenerEvent {        @Async    @EventListener({ UserChangePasswordEvent.class })    public void logListener(UserChangePasswordEvent event) {        System.out.println("收到事件:" + event);        System.out.println("开始执行业务操作生成关键日志。用户userId为:" + event.getUserId());    }}

这样我们的异步执行监听器的业务操作就完成了。oQ028资讯网——每日最新资讯28at.com

oQ028资讯网——每日最新资讯28at.com

Spring的事件机制的应用场景

  • 告警操作,比喻钉钉告警,异常告警,可以通过事件机制进行解耦;
  • 关键性日志记录和业务埋点,比喻说我们的关键日志需要入库,记录一下操作时间,操作人,变更内容等等,可以通过事件机制进行解耦;
  • 性能监控,比喻说一些接口的时长,性能方便的埋点等可以通过事件机制进行解耦;
  • 一切与主业务无关的操作都可以通过这种方式进行解耦,常用的场景大概就上述提到的,而且很多架构的源码都有使用这种机制,如GateWay,Spring等等;

oQ028资讯网——每日最新资讯28at.com

Spring的事件机制的注意事项

  • 对于同一个事件,有多个监听器的时候,注意可以通过@Order注解指定顺序,Order的value值越小,执行的优先级就越高;
  • 如果发布事件的方法处于事务中,那么事务会在监听器方法执行完毕之后才提交事件发布之后就由监听器去处理,而不要影响原有的事务,也就是说希望事务及时提交我们就可以@TransactionalEventListener来定义一个监听器;
  • 监听器默认是同步执行的,如果我们想实现异步执行,可以搭配@Async注解使用,但是前提条件是你真的懂@Async注解,使用不当会出现问题的;
  • 对于同一个事件,有多个监听器的时候,如果出现了异常,后续的监听器就失效了,因为他是把同一个事件的监听器add在一个集合里面循环执行,如果出现异常,需要注意捕获异常处理异常;

本文链接:http://www.28at.com/showinfo-26-10471-0.html使用上 Spring 的事件机制,真香!

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 抛砖引玉:Redis 与 接口自动化测试框架的结合

下一篇: Python应知小技巧:如何用更少的代码做更多的事情

标签:
  • 热门焦点
  • 鸿蒙OS 4.0公测机型公布:甚至连nova6都支持

    鸿蒙OS 4.0公测机型公布:甚至连nova6都支持

    华为全新的HarmonyOS 4.0操作系统将于今天下午正式登场,官方在发布会之前也已经正式给出了可升级的机型产品,这意味着这些机型会率先支持升级享用。这次的HarmonyOS 4.0支持
  • 小米平板5 Pro 12.4简评:多专多能 兼顾影音娱乐的大屏利器

    小米平板5 Pro 12.4简评:多专多能 兼顾影音娱乐的大屏利器

    疫情带来了网课,网课盘活了安卓平板,安卓平板市场虽然中途停滞了几年,但好的一点就是停滞的这几年行业又有了新的发展方向,例如超窄边框、高刷新率、多摄镜头组合等,这就让安卓
  • 天猫精灵Sound Pro体验:智能音箱没有音质?来听听我的

    天猫精灵Sound Pro体验:智能音箱没有音质?来听听我的

    这几年除了手机作为智能生活终端最主要的核心之外,第二个可以成为中心点的产品是什么?——是智能音箱。 手机在执行命令的时候有两种操作方式,手和智能语音助手,而智能音箱只
  • Rust中的高吞吐量流处理

    Rust中的高吞吐量流处理

    作者 | Noz编译 | 王瑞平本篇文章主要介绍了Rust中流处理的概念、方法和优化。作者不仅介绍了流处理的基本概念以及Rust中常用的流处理库,还使用这些库实现了一个流处理程序
  • K8S | Service服务发现

    K8S | Service服务发现

    一、背景在微服务架构中,这里以开发环境「Dev」为基础来描述,在K8S集群中通常会开放:路由网关、注册中心、配置中心等相关服务,可以被集群外部访问;图片对于测试「Tes」环境或者
  • 雅柏威士忌多款单品价格大跌,泥煤顶流也不香了?

    雅柏威士忌多款单品价格大跌,泥煤顶流也不香了?

    来源 | 烈酒商业观察编 | 肖海林今年以来,威士忌市场开始出现了降温迹象,越来越多不断暴涨的网红威士忌也开始悄然回归市场理性。近日,LVMH集团旗下苏格兰威士忌品牌雅柏(Ardbeg
  • 2纳米决战2025

    2纳米决战2025

    集微网报道 从三强争霸到四雄逐鹿,2nm的厮杀声已然隐约传来。无论是老牌劲旅台积电、三星,还是誓言重回先进制程领先地位的英特尔,甚至初成立不久的新
  • Windows 11发布,微软一改往常对老机型开放的态度

    Windows 11发布,微软一改往常对老机型开放的态度

    距离 Windows 11 发布已经过去一周,在过去一周里,很多数码爱好者围绕其对 Android 应用的支持、对老机型的升级问题展开了激烈讨论。与以往不同的是,在这次大
  • 上海举办人工智能大会活动,建设人工智能新高地

    上海举办人工智能大会活动,建设人工智能新高地

    人工智能大会在上海浦江两岸隆重拉开帷幕,人工智能新技术、新产品、新应用、新理念集中亮相。8月30日晚,作为大会的特色活动之一的上海人工智能发展盛典人工
Top