您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

使用了Spring的事件机制真香!

时间:2023-09-07 12:28:39  来源:  作者:

前言

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

Spring的事件机制的基本概念

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

举个例子:用户修改密码,修改完密码后需要短信通知用户,记录关键性日志,等等其他业务操作。

如下图,就是我们需要调用多个服务来进行实现一个修改密码的功能。

使用了事件机制后,我们只需要发布一个事件,无需关心其扩展的逻辑,让我们的事件监听器去处理,从而实现了模块之间的解耦。

事件

通过继承ApplicationEvent,实现自定义事件。是对 JAVA EventObject 的扩展,表示 Spring 的事件,Spring 中的所有事件都要基于其进行扩展。其源码如下。

我们可以获取到timestamp属性指的是发生时间。

事件发布

事件发布是事件发生的地方,它负责产生事件并通知事件监听器。ApplicationEventPublisher用于用于发布 ApplicationEvent 事件,发布后 ApplicationListener 才能监听到事件进行处理。源码如下。

需要一个ApplicationEvent,就是我们的事件,来进行发布事件。

事件监听器

ApplicationListener 是 Spring 事件的监听器,用来接受事件,所有的监听器都必须实现该接口。该接口源码如下。

Spring的事件机制的使用方法

下面会给大家演示如何去使用Spring的事件机制。就拿修改密码作为演示。

如何定义一个事件

新增一个类,继承我们的ApplicationEvent。

如下面代码,继承后定义了一个userId,有一个UserChangePasswordEvent方法。这里就定义我们监听器需要的业务参数,监听器需要那些参数,我们这里就定义那些参数。

/**
 * @Author JiaQIng
 * @Description 修改密码事件
 * @ClassName UserChangePasswordEvent
 * @Date 2023/3/26 13:55
 **/
@Getter
@Setter
public class UserChangePasswordEvent extends ApplicationEvent {
    private String userId;


    public UserChangePasswordEvent(String userId) {
        super(new Object());
        this.userId = userId;
    }
}

如何监听事件

实现监听器有两种方法

  1. 新建一个类实现ApplicationListener接口,并且重写onApplicationEvent方法。注入到Spring容器中,交给Spring管理。如下代码。新建了一个发送短信监听器,收到事件后执行业务操作。****
/**
 * @Author JiaQIng
 * @Description 发送短信监听器
 * @ClassName MessageListener
 * @Date 2023/3/26 14:16
 **/
@Component
public class MessageListener implements ApplicationListener<UserChangePasswordEvent> {


    @Override
    public void onApplicationEvent(UserChangePasswordEvent event) {
        System.out.println("收到事件:" + event);
        System.out.println("开始执行业务操作给用户发送短信。用户userId为:" + event.getUserId());
    }
}
  1. 使用 @EventListener 注解标注处理事件的方法,此时 Spring 将创建一个 ApplicationListener bean 对象,使用给定的方法处理事件。源码如下。参数可以给指定的事件。这里巧妙的用到了@AliasFor的能力,放到了@EventListener身上 注意:一般建议都需要指定此值,否则默认可以处理所有类型的事件,范围太广了。

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

/**
 * @Author JiaQIng
 * @Description 事件监听器
 * @ClassName LogListener
 * @Date 2023/3/26 14:22
 **/
@Component
public 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());
    }
}
  1. @TransactionalEventListener来定义一个监听器,他与@EventListener不同的就是@EventListener标记一个方法作为监听器,他默认是同步执行,如果发布事件的方法处于事务中,那么事务会在监听器方法执行完毕之后才提交。事件发布之后就由监听器去处理,而不要影响原有的事务,也就是说希望事务及时提交。我们就可以使用该注解来标识。注意此注解需要spring-tx的依赖。

注解源码如下:主要是看一下注释内容。

// 在这个注解上面有一个注解:`@EventListener`,所以表明其实这个注解也是个事件监听器。 
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @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指定事件。

/**
 * @Author JiaQIng
 * @Description 事件监听器
 * @ClassName LogListener
 * @Date 2023/3/26 14:22
 **/
@Component
public 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());
    }
}

如何发布一个事件

  1. 使用ApplicationContext进行发布,由于ApplicationContext 已经继承了 ApplicationEventPublisher ,因此可以直接使用发布事件。源码如下

  1. 直接注入我们的ApplicationEventPublisher,使用@Autowired注入一下。

三种发布事件的方法,我给大家演示一下@Autowired注入的方式发布我们的事件。

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


}

我们执行一下看一下接口。

测试成功。

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

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

使用@Async时,需要配置线程池,否则用的还是默认的线程池也就是主线程池,线程池使用不当会浪费资源,严重的会出现OOM事故。

下图是阿里巴巴开发手册的强制要求。

简单的演示一下:这里声明一下俺没有使用线程池,只是简单的演示一下。

  1. 在我们的启动类上添加@EnableAsync开启异步执行配置
@EnableAsync
@SpringBootApplication
public class SpirngEventApplication {


    public static void mAIn(String[] args) {
        SpringApplication.run(SpirngEventApplication.class, args);
    }


}
  1. 在我们想要异步执行的监听器上添加@Async注解。
 
/**
 * @Author JiaQIng
 * @Description 事件监听器
 * @ClassName LogListener
 * @Date 2023/3/26 14:22
 **/
@Component
public class ListenerEvent {
    
    @Async
    @EventListener({ UserChangePasswordEvent.class })
    public void logListener(UserChangePasswordEvent event) {
        System.out.println("收到事件:" + event);
        System.out.println("开始执行业务操作生成关键日志。用户userId为:" + event.getUserId());
    }
}

 

这样我们的异步执行监听器的业务操作就完成了。

Spring的事件机制的应用场景

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

Spring的事件机制的注意事项

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

后记

此文章主要是讲解什么是Spring的事件机制,怎么使用Spring事件机制,工作中的场景有哪些。



Tags:Spring   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  Search: Spring  点击:(53)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  Search: Spring  点击:(39)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  Search: Spring  点击:(10)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  Search: Spring  点击:(84)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19  Search: Spring  点击:(86)  评论:(0)  加入收藏
Spring Boot2.0深度实践 核心原理拆解+源码分析
Spring Boot2.0深度实践:核心原理拆解与源码分析一、引言Spring Boot是一个基于Java的轻量级框架,它简化了Spring应用程序的创建过程,使得开发者能够快速搭建一个可运行的应用...【详细内容】
2024-01-15  Search: Spring  点击:(93)  评论:(0)  加入收藏
SpringBoot3+Vue3 开发高并发秒杀抢购系统
开发高并发秒杀抢购系统:使用SpringBoot3+Vue3的实践之旅随着互联网技术的发展,电商行业对秒杀抢购系统的需求越来越高。为了满足这种高并发、高流量的场景,我们决定使用Spring...【详细内容】
2024-01-14  Search: Spring  点击:(90)  评论:(0)  加入收藏
Spring Boot 3.0是什么?
Spring Boot 3.0是一款基于Java的开源框架,用于简化Spring应用程序的构建和开发过程。与之前的版本相比,Spring Boot 3.0在多个方面进行了改进和增强,使其更加易用、高效和灵活...【详细内容】
2024-01-11  Search: Spring  点击:(132)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11  Search: Spring  点击:(124)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  Search: Spring  点击:(115)  评论:(0)  加入收藏
▌简易百科推荐
对于微服务架构监控应该遵守的原则
随着软件交付方式的变革,微服务架构的兴起使得软件开发变得更加快速和灵活。在这种情况下,监控系统成为了微服务控制系统的核心组成部分。随着软件的复杂性不断增加,了解系统的...【详细内容】
2024-04-03  步步运维步步坑    Tags:架构   点击:(5)  评论:(0)  加入收藏
大模型应用的 10 种架构模式
作者 | 曹洪伟在塑造新领域的过程中,我们往往依赖于一些经过实践验证的策略、方法和模式。这种观念对于软件工程领域的专业人士来说,已经司空见惯,设计模式已成为程序员们的重...【详细内容】
2024-03-27    InfoQ  Tags:架构模式   点击:(13)  评论:(0)  加入收藏
哈啰云原生架构落地实践
一、弹性伸缩技术实践1.全网容器化后一线研发的使用问题全网容器化后一线研发会面临一系列使用问题,包括时机、容量、效率和成本问题,弹性伸缩是云原生容器化后的必然技术选择...【详细内容】
2024-03-27  哈啰技术  微信公众号  Tags:架构   点击:(10)  评论:(0)  加入收藏
DDD 与 CQRS 才是黄金组合
在日常工作中,你是否也遇到过下面几种情况: 使用一个已有接口进行业务开发,上线后出现严重的性能问题,被老板当众质疑:“你为什么不使用缓存接口,这个接口全部走数据库,这怎么能扛...【详细内容】
2024-03-27  dbaplus社群    Tags:DDD   点击:(11)  评论:(0)  加入收藏
高并发架构设计(三大利器:缓存、限流和降级)
软件系统有三个追求:高性能、高并发、高可用,俗称三高。本篇讨论高并发,从高并发是什么到高并发应对的策略、缓存、限流、降级等。引言1.高并发背景互联网行业迅速发展,用户量剧...【详细内容】
2024-03-13    阿里云开发者  Tags:高并发   点击:(6)  评论:(0)  加入收藏
如何判断架构设计的优劣?
架构设计的基本准则是非常重要的,它们指导着我们如何构建可靠、可维护、可测试的系统。下面是这些准则的转换表达方式:简单即美(KISS):KISS原则的核心思想是保持简单。在设计系统...【详细内容】
2024-02-20  二进制跳动  微信公众号  Tags:架构设计   点击:(36)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  ijunfu  今日头条  Tags:SpringBoot   点击:(10)  评论:(0)  加入收藏
PHP+Go 开发仿简书,实战高并发高可用微服务架构
来百度APP畅享高清图片//下栽のke:chaoxingit.com/2105/PHP和Go语言结合,可以开发出高效且稳定的仿简书应用。在实现高并发和高可用微服务架构时,我们可以采用一些关键技术。首...【详细内容】
2024-01-14  547蓝色星球    Tags:架构   点击:(115)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11    王建立  Tags:Spring Boot   点击:(124)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  互联网架构小马哥    Tags:Spring Boot   点击:(115)  评论:(0)  加入收藏
站内最新
站内热门
站内头条