从观察者模式谈论 : Spring Boot中创建、发布和侦听自定义事件

简介: 从观察者模式谈论 : Spring Boot中创建、发布和侦听自定义事件

目录

观察者模式

观察者模式和发布订阅模式区别

Spring事件监听机制

Spring 事件机制实现的几个重要类

为什么我应该使用事件而不是直接方法调用?

什么是应用程序事件( Application Events)?

Spring Boot 应用程序中创建、发布和侦听自定义事件

创建ApplicationEvent

发布一个ApplicationEvent

接收应用程序事件

注解

实现ApplicationListener接口

异步事件侦听器

Transaction-绑定事件

Spring Boot的 Application Events

ApplicationStartingEvent

ApplicationEnvironmentPreparedEvent

ApplicationContextInitializedEvent

ApplicationPreparedEvent

WebServerInitializedEvent

ApplicationStartedEvent

ApplicationReadyEvent

ApplicationFailedEvent

结论



观察者模式

观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。

此种模式通常被用来实时事件处理系统。

在我们日常业务开发中,观察者模式对我们很大的一个作用,在于实现业务的解耦。以用户注册的场景来举例子,假设在用户注册完成时,需要给该用户发送邮件、发送优惠劵等等操作.


观察者模式和发布订阅模式区别

观察者模式里,只有两个角色 —— 观察者 + 被观察者; 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个管理并执行消息队列的“经纪人Broker”

观察者和被观察者,是松耦合的关系;发布者和订阅者,则完全不存在耦合

  • 观察者模式:数据源直接通知订阅者发生改变。
  • 发布订阅模式:数据源告诉第三方(事件频道)发生了改变,第三方再通知订阅者发生了改变。
  • 在设计模式结构上,发布订阅模式继承自观察者模式,是观察者模式的一种实现的变体。
  • 在设计模式意图上,两者关注点不同,一个关心数据源,一个关心的是事件消息。

至于是否是优化和改进,还是取决于业务场景:

比如我就是要观察某个数据实例,我明确关心的是具体的数据源,我只想知道属于这个数据源的数据变化,我肯定希望他发生任何改变直接通知我,而不是过一遍第三方。

那么发布订阅什么时候用呢,我第一关心的不是数据源是谁从哪里来,我只是对某个定义好的事件或消息感兴趣,那么我就需要一个第三方(事件频道)去定义事件的规范,接受事件发布和事件订阅,只要谁发出了这种事件或消息,我就要进行相应的处理。


Spring事件监听机制

要“监听”事件,我们总是可以将“监听器”作为事件源中的另一个方法写入事件,但这将使事件源与监听器的逻辑紧密耦合。

对于实际事件,我们比直接方法调用更灵活。我们可以根据需要动态注册和注销某些事件的侦听器。我们还可以为同一事件设置多个侦听器。

本教程概述了如何发布和侦听自定义事件,并解释了 Spring Boot 的内置事件。


Spring 事件机制实现的几个重要类

类名 描述
ApplicationEvent 继承自 java.util.EventObject,Spring 事件的基类,是个抽象类
ApplicationEventPublisher 事件发布者,封装了事件发布功能,调用广播发布事件
ApplicationEventMulticaster 广播,持有观察者(也就是 ApplicationListener)的集合,可以向集合类的观察者们通知事件的发生
ApplicationListener 观察者,接收到事件发生的情况后,执行相关的业务逻辑, 继承自 java.util.EventListener


为什么我应该使用事件而不是直接方法调用?

事件和直接方法调用都适合于不同的情况。使用方法调用,就像断言一样-无论发送和接收模块的状态如何,他们都需要知道此事件的发生。

对于事件,另一方面,我们只知道发生了一个事件,哪些模块会被通知并不是我们关心的问题。当我们想要将某些业务处理传递给另一个线程时(例如:在某些任务完成时发送电子邮件),最好使用事件。此外,事件对于测试驱动的开发也很有用。


什么是应用程序事件( Application Events)?

Spring 应用程序事件允许我们发送和接收特定应用程序事件,我们可以根据需要处理这些事件。事件用于在松散耦合的组件之间交换信息。由于发布者和订阅者之间没有直接耦合,因此可以在不影响发布者的情况下修改订阅者,反之亦然。

让我们看看如何在 Spring Boot 应用程序中创建、发布和侦听自定义事件。


Spring Boot 应用程序中创建、发布和侦听自定义事件

创建ApplicationEvent

我们可以使用 Spring Framework 的事件发布机制发布应用程序事件。

让我们通过扩展来创建调用的自定义事件:

class UserCreatedEvent extends ApplicationEvent {
  private String name;
  UserCreatedEvent(Object source, String name) {
    super(source);
    this.name = name;
  }
  ...
}

代码中super(source)中的source应该是最初发生事件的对象或与事件相关联的对象。

从Spring 4.2开始,我们还可以将对象发布为事件,而无需扩展ApplicationEvent:

class UserRemovedEvent {
  private String name;
  UserRemovedEvent(String name) {
    this.name = name;
  }
  ...
}


发布一个ApplicationEvent

我们使用ApplicationEventPublisher接口发布事件:

@Component
class Publisher {
  private final ApplicationEventPublisher publisher;
    Publisher(ApplicationEventPublisher publisher) {
      this.publisher = publisher;
    }
  void publishEvent(final String name) {
    // Publishing event created by extending ApplicationEvent
    publisher.publishEvent(new UserCreatedEvent(this, name));
    // Publishing an object as an event
    publisher.publishEvent(new UserRemovedEvent(name));
  }
}

当我们发布的对象不是ApplicationEvent时,Spring会自动为我们将其包装在PayloadApplicationEvent中。


接收应用程序事件

现在,我们知道如何创建和发布自定义事件,让我们看看如何侦听该事件。事件可以有多个侦听器并且根据应用程序要求执行不同的工作。

有两种方法可以定义侦听器。我们可以使用注解(@EventListener)或实现接口(ApplicationListener)。在这两种情况下,侦听器类都必须由 Spring 管理。


注解

从Spring 4.1开始,可以使用@EventListener注解的方法,以自动注册与该方法签名匹配的ApplicationListener:

@Component
class UserRemovedListener {
  @EventListener
  ReturnedEvent handleUserRemovedEvent(UserRemovedEvent event) {
    // handle UserRemovedEvent ...
    return new ReturnedEvent();
  }
  @EventListener
  void handleReturnedEvent(ReturnedEvent event) {
        // handle ReturnedEvent ...
  }
  ...
}

启用注解驱动的配置时,不需要其他配置。我们的方法可以监听多个事件,或者如果我们想完全不使用任何参数来定义它,那么事件类型也可以在注解本身上指定。示例:@EventListener({ContextStartedEvent.class,ContextRefreshedEvent.class})。

对于使用@EventListener注解并定义为具有返回类型的方法,Spring会将结果作为新事件发布给我们。在上面的示例中,第一个方法返回的ReturnedEvent将被发布,然后由第二个方法处理。

如果指定SpEL条件,Spring仅在某些情况下才允许触发我们的侦听器

@Component
class UserRemovedListener {
  @EventListener(condition = "#event.name eq 'reflectoring'")
  void handleConditionalListener(UserRemovedEvent event) {
    // handle UserRemovedEvent
  }
}

仅当表达式的计算结果为true或以下字符串之一时才处理该事件:“ true”,“ on”,“ yes”或“ 1”。方法参数通过其名称公开。条件表达式还公开了一个“ root”变量,该变量引用原始ApplicationEvent(#root.event)和实际方法参数(#root.args)

在以上示例中,仅当#event.name的值为'reflectoring'时,才会使用UserRemovedEvent触发监听器。


实现ApplicationListener接口

侦听事件的另一种方法是实现ApplicationListener接口:

@Component
class UserCreatedListener implements ApplicationListener<UserCreatedEvent> {
  @Override
  public void onApplicationEvent(UserCreatedEvent event) {
    // handle UserCreatedEvent
  }
}

只要侦听器对象在Spring应用程序上下文中注册,它就会接收事件。当Spring路由一个事件时,它使用侦听器的签名来确定它是否与事件匹配。


异步事件侦听器

默认情况下,spring事件是同步的,这意味着发布者线程将阻塞,直到所有侦听器都完成对事件的处理为止。

要使事件侦听器以异步模式运行,我们要做的就是在该侦听器上使用@Async注解:

@Component
class AsyncListener {
  @Async
  @EventListener
  void handleAsyncEvent(String event) {
    // handle event
  }
}

为了使@Async注解起作用,我们还必须使用@EnableAsync注解我们的@Configuration类之一或@SpringBootApplication类。

上面的代码示例还显示了我们可以将String用作事件。使用风险自负。最好使用特定于我们用例的数据类型,以免与其他事件冲突。


Transaction-绑定事件

Spring允许我们将事件侦听器绑定到当前事务的某个阶段。如果当前事务的结果对侦听器很重要时,这使事件可以更灵活地使用。

当我们使用@TransactionalEventListener注释方法时,我们将获得一个扩展的事件侦听器,该侦听器可以了解事务:

@Component
class UserRemovedListener {
  @TransactionalEventListener(phase=TransactionPhase.AFTER_COMPLETION)
  void handleAfterUserRemoved(UserRemovedEvent event) {
    // handle UserRemovedEvent
  }
}

仅当当前事务完成时才调用UserRemovedListener。

我们可以将侦听器绑定到事务的以下阶段:

  • AFTER_COMMIT:事务成功提交后,将处理该事件。如果事件侦听器仅在当前事务成功时才运行,则可以使用此方法。
  • AFTER_COMPLETION:事务提交或回滚时将处理该事件。例如,我们可以使用它在事务完成后执行清理。
  • AFTER_ROLLBACK:事务回滚后将处理该事件。
  • BEFORE_COMMIT:该事件将在事务提交之前进行处理。例如,我们可以使用它来将事务性ORM会话刷新到数据库。


Spring Boot的 Application Events

Spring Boot提供了几个与SpringApplication生命周期相关的预定义ApplicationEvent

在创建ApplicationContext之前会触发一些事件,因此我们无法将这些事件注册为@Bean。我们可以通过手动添加侦听器来注册这些事件的侦听器:

@SpringBootApplication
public class EventsDemoApplication {
  public static void main(String[] args) {
    SpringApplication springApplication = 
        new SpringApplication(EventsDemoApplication.class);
    springApplication.addListeners(new SpringBuiltInEventsListener());
    springApplication.run(args);
  }
}

通过将META-INF/spring.factories文件添加到我们的项目中,我们还可以注册侦听器,而不管如何创建应用程序,并使用org.springframework.context.ApplicationListener键引用侦听器:

org.springframework.context.ApplicationListener= com.reflectoring.eventdemo.SpringBuiltInEventsListener

class SpringBuiltInEventsListener 
    implements ApplicationListener<SpringApplicationEvent>{
  @Override
  public void onApplicationEvent(SpringApplicationEvent event) {
    // handle event
  }
}

确定事件监听器已正确注册后,便可以监听所有Spring Boot的SpringApplicationEvents。让我们按照它们在应用程序启动过程中的执行顺序来进行观察。


ApplicationStartingEvent

除了运行侦听器和初始化程序的注册之外,ApplicationStartingEvent在运行开始时但在任何处理之前都会触发。


ApplicationEnvironmentPreparedEvent

当上下文中使用的环境可用时,将触发ApplicationEnvironmentPreparedEvent。

由于此时环境已准备就绪,因此我们可以在其他Bean使用它之前对其进行检查和修改。


ApplicationContextInitializedEvent

当ApplicationContext准备就绪并且调用ApplicationContextInitializers但尚未加载bean定义时,将触发ApplicationContextInitializedEvent。

在bean初始化到Spring容器之前,我们可以使用它来执行任务。


ApplicationPreparedEvent

准备好ApllicationContext但未刷新时会触发ApplicationPreparedEvent。

该环境已准备就绪,可以使用,并且将加载Bean定义。


WebServerInitializedEvent

如果我们使用的是网络服务器,则在网络服务器准备就绪后会触发WebServerInitializedEvent。 ServletWebServerInitializedEvent和ReactiveWebServerInitializedEvent分别是servlet和反应式网络服务。

WebServerInitializedEvent不扩展SpringApplicationEvent。


ApplicationStartedEvent

在刷新上下文之后但在调用任何应用程序和命令行运行程序之前,将触发ApplicationStartedEvent。


ApplicationReadyEvent

触发ApplicationReadyEvent来指示该应用程序已准备就绪,可以处理请求。

建议此时不要修改内部状态,因为所有初始化步骤都将完成。


ApplicationFailedEvent

如果存在异常并且应用程序无法启动,则会触发ApplicationFailedEvent。在启动期间的任何时间都可能发生这种情况。

我们可以使用它来执行一些任务,例如执行脚本或在启动失败时发出通知。


结论

事件是为在同一应用程序上下文内的Spring Bean之间进行简单通信而设计的。从Spring 4.2开始,基础结构已得到显着改进,并提供了基于注解的模型以及发布任意事件的功能。

① 如果想要多个监听器按照指定顺序执行,可以通过实现 Ordered 接口,指定其顺序。

② 如果胖友想要监听多种 ApplicationContext 事件,可以实现 SmartApplicationListener 接口,具体示例可以看看 SourceFilteringListener 类。

@TransactionalEventListener 注解,可以声明在当前事务“结束”时,执行相应的监听逻辑。

④ 可以通过实现 ApplicationEventMulticaster 接口,定义自定义的事件广播器,可以往里面添加和移除监听器,并发布事件给注册在其中的监听器。使用比较少,基本可以忽略。


译文链接:https://reflectoring.io/spring-boot-application-events-explained/

参考链接: https://www.iocoder.cn/Spring-Boot/Event/


目录
相关文章
|
5月前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
129 0
|
5月前
SpringBoot+Mybatis-Plus+PageHelper分页+多条件查询
SpringBoot+Mybatis-Plus+PageHelper分页+多条件查询
135 0
|
5月前
|
XML Java 数据库连接
Spring Boot的数据访问之Spring Data JPA以及Hibernate的实战(超详细 附源码)
Spring Boot的数据访问之Spring Data JPA以及Hibernate的实战(超详细 附源码)
87 0
|
6天前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
21 2
|
16天前
|
消息中间件 设计模式 缓存
spring源码设计模式分析(四)-观察者模式
spring源码设计模式分析(四)-观察者模式
|
4月前
|
运维 Java 关系型数据库
Spring运维之boot项目bean属性的绑定读取与校验
Spring运维之boot项目bean属性的绑定读取与校验
49 2
|
4月前
|
存储 运维 Java
Spring运维之boot项目开发关键之日志操作以及用文件记录日志
Spring运维之boot项目开发关键之日志操作以及用文件记录日志
53 2
|
4月前
|
Java Maven
springboot项目打jar包后,如何部署到服务器
springboot项目打jar包后,如何部署到服务器
286 1
|
4月前
|
XML 运维 Java
Spring运维之boot项目打包jar和插件运行并且设置启动时临时属性和自定义配置文件
Spring运维之boot项目打包jar和插件运行并且设置启动时临时属性和自定义配置文件
43 1
|
4月前
springboot2.4.5使用pagehelper分页插件
springboot2.4.5使用pagehelper分页插件
112 0