首页
Preview

Spring 应用程序事件机制

什么是Spring Event?何时使用它?如何很好地使用它?

什么是Spring Event

Spring事件机制是Spring框架的一个特性,它允许组件以松散耦合的方式进行通信。它基于观察者设计模式,其中一个对象(主题)维护其依赖项(观察者)列表,并自动通知它们任何状态更改。在Spring中,事件由ApplicationEvent类或其子类的实例表示。

Spring中观察者模式的核心类包括:

  • ApplicationListener:一个应用程序侦听器或观察者,它扩展了JDK中的EventListener。当正在侦听的事件发生时,这个类中唯一的方法onApplication()将被调用。
  • ApplicationEvent:一个抽象事件类,它扩展了JDK中的EventObject。Spring和SpringBoot中的所有事件都是这个类的子类。
  • ApplicationEventMulticaster:事件注册和广播中心,用于注册侦听器和广播事件。
  • ApplicationContext:Spring中的IOC容器,它也是一个发布者,因为ApplicationContext扩展了ApplicationEventPublisher并可以通过publishEvent(Object event)方法发布事件。

这些类一起形成了Spring中观察者模式的核心,允许在框架内进行灵活的事件驱动编程。

在Spring Event机制下,Spring应用程序中的组件可以在不直接了解彼此的情况下相互通信。这允许采用更灵活和松散耦合的架构,可以轻松地添加、删除或替换组件,而不会影响系统中的其他组件。

下图显示了Spring源代码中事件发布工作流程。

好的,让我们来看看Spring事件的核心类:

ApplicationEvent

ApplicationEvent是Spring框架中的一个抽象类,用作Spring中所有应用级事件的基类。它提供了在Spring框架中发布和使用事件的一致API。

ApplicationEvent类提供了几个构造函数,允许子类使用必要的信息初始化事件。这包括事件的来源,通常是发布事件的对象,以及事件可能需要携带的任何其他数据。

通过扩展ApplicationEvent类,开发人员可以创建自己的自定义事件,并使用Spring事件机制发布它们。这允许采用灵活和可扩展的架构,其中不同的组件可以通过事件相互通信,而不是通过直接方法调用或其他形式的耦合。

下图显示了上图中每个ApplicationEvent的主要应用场景:

  • ServletRequestHandledEvent:这个事件是Spring Web MVC框架在@RequestMapping方法处理请求后发布的,类似于RequestHandledEvent。但是,与RequestHandledEvent不同,ServletRequestHandledEvent提供了有关请求和响应对象的更详细信息。
  • PayloadApplicationEvent:spring-context模块类,早期Spring仅支持发布ApplicationEvent类型事件,自Spring 4.2以来可以发布非ApplicationEvent类型事件,当事件参数将被包装到PayloadApplicationEvent中,然后发布。
  • ApplicationContextEvent:这是Spring框架中发布的一种事件类型,由应用程序上下文在其状态更改时发布。此事件用于跟踪应用程序上下文的生命周期,并在初始化、刷新或关闭上下文时执行某些操作。
  • ContextStartedEvent:当应用程序上下文启动时,通常在上下文上调用start()方法时,会发布此事件。
  • ContextStoppedEvent:当应用程序上下文停止时,通常在上下文上调用stop()方法时,会发布此事件。
  • ContextRefreshedEvent:当应用程序上下文初始化或刷新时,会发布此事件。这通常发生在应用程序启动时,或者在手动刷新上下文时发生。
  • ContextClosedEvent:当应用程序上下文关闭时,通常在上下文上调用close()方法时,会发布此事件。

通过监听这些事件,开发人员可以在应用程序上下文生命周期的不同阶段执行某些操作。例如,他们可以在上下文刷新时初始化bean,在上下文启动时启动后台线程,或在上下文关闭时释放资源。

SpringBoot中的事件

Spring Boot基于Spring框架构建,并提供了更多的功能和约定,使得创建生产就绪的应用程序更加容易。Spring Boot利用Spring事件的一种方式是提供预构建的事件侦听器,可以直接使用。

例如,Spring Boot提供了一个SpringApplication类,用于引导应用程序上下文并启动应用程序。这个类还在应用程序启动和关闭过程中发布了几个事件,包括:

  • ApplicationStartingEvent:此事件在创建应用程序上下文之前的应用程序启动过程的最开始发布。
  • ApplicationEnvironmentPreparedEvent:此事件在创建应用程序上下文之后,但在准备环境之前发布。
  • ApplicationPreparedEvent:此事件在准备环境之后,但在刷新应用程序上下文之前发布。
  • ApplicationStartedEvent:此事件在应用程序上下文刷新并启动应用程序时发布。
  • ApplicationReadyEvent:此事件在应用程序启动并准备好为请求提供服务时发布。
  • ApplicationFailedEvent:如果应用程序启动失败,则会发布此事件。

当Spring Boot应用程序启动时,它使用一个称为EventPublishingRunListener的类来向Spring的事件机制发布事件。这个类是ApplicationListener接口的Spring Boot特定实现,用于侦听特定的应用程序事件,并将它们发布到Spring的事件机制。

当EventPublishingRunListener从SpringApplication接收到一个开始状态事件时,它使用SimpleApplicationEventMulticaster将事件广播到所有已注册的Spring侦听器。这是Spring事件模型使用的相同机制,它允许Spring Boot利用Spring提供的现有基础设施来管理事件。

因此,总之,Spring Boot的事件机制通过使用Spring Boot特定的ApplicationListener接口的Spring Boot特定实现来利用Spring事件模型,向现有的Spring事件基础设施发布事件。好的,让我们来看看核心源代码:

EventPublishingRunListener

class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

 private final SpringApplication application;

 private final String[] args;

 private final SimpleApplicationEventMulticaster initialMulticaster;

 EventPublishingRunListener(SpringApplication application, String[] args) {
  this.application = application;
  this.args = args;
  this.initialMulticaster = new SimpleApplicationEventMulticaster();
 }

 @Override
 public int getOrder() {
  return 0;
 }

 @Override
 public void starting(ConfigurableBootstrapContext bootstrapContext) {
  multicastInitialEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
 }

 @Override
 public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
   ConfigurableEnvironment environment) {
  multicastInitialEvent(
    new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
 }

 @Override
 public void contextPrepared(ConfigurableApplicationContext context) {
  multicastInitialEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
 }

 @Override
 public void contextLoaded(ConfigurableApplicationContext context) {
  for (ApplicationListener<?> listener : this.application.getListeners()) {
   if (listener instanceof ApplicationContextAware contextAware) {
    contextAware.setApplicationContext(context);
   }
   context.addApplicationListener(listener);
  }
  multicastInitialEvent(new ApplicationPreparedEvent(this.application, this.args, context));
 }

 @Override
 public void started(ConfigurableApplicationContext context, Duration timeTaken) {
  context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, timeTaken));
  AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
 }

 @Override
 public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
  context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken));
  AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
 }

 @Override
 public void failed(ConfigurableApplicationContext context, Throwable exception) {
  ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
  if (context != null && context.isActive()) {
   // Listeners have been registered to the application context so we should
   // use it at this point if we can
   context.publishEvent(event);
  }
  else {
   // An inactive context may not have a multicaster so we use our multicaster to
   // call all the context's listeners instead
   if (context instanceof AbstractApplicationContext abstractApplicationContext) {
    for (ApplicationListener<?> listener : abstractApplicationContext.getApplicationListeners()) {
     this.initialMulticaster.addApplicationListener(listener);
    }
   }
   this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
   this.initialMulticaster.multicastEvent(event);
  }
 }

 private void multicastInitialEvent(ApplicationEvent event) {
  refreshApplicationListeners();
  this.initialMulticaster.multicastEvent(event);
 }

 private void refreshApplicationListeners() {
  this.application.getListeners().forEach(this.initialMulticaster::addApplicationListener);
 }

 private static class LoggingErrorHandler implements ErrorHandler {

  private static final Log logger = LogFactory.getLog(EventPublishingRunListener.class);

  @Override
  public void handleError(Throwable throwable) {
   logger.warn("Error calling ApplicationEventListener", throwable);
  }

 }

}

如你所见,EventPublishingRunListener 实现了 SpringApplicationRunListener 接口,该接口定义了在 Spring Boot 应用程序启动过程中调用的一组方法。

当构造 EventPublishingRunListener 时,它会创建一个新的 SimpleApplicationEventMulticaster 并添加所有在 SpringApplication 实例中注册的应用程序侦听器。

EventPublishingRunListener 中的每个方法都对应于 Spring Boot 启动过程中的特定事件。

例如:

  • 当应用程序上下文已启动时,将调用 started() 方法。
  • 当应用程序准备好接收请求时,将调用 running() 方法。

在每个方法中,EventPublishingRunListener 使用在构造期间创建的 ApplicationEventMulticaster 实例将适当的事件发布到 Spring 框架的事件基础设施中。

总的来说,EventPublishingRunListener 是 Spring Boot 启动过程的关键组件,它允许事件发布到 Spring 框架的事件机制中,从而使其他组件和应用程序可以监听和响应这些事件。

那么,Spring 事件机制能为我们带来什么呢?

何时使用

当你想要解耦应用程序中的组件并允许它们相互通信而不直接知道彼此时,应使用 Spring 事件机制。这可以使你的应用程序更加灵活和易于维护。你可以使用事件来触发对状态更改的操作,例如更新缓存或发送通知。你还可以使用事件在应用程序中传播异常或其他错误条件。

以下是你可能感兴趣的许多场景:

  • 基于某些事件触发操作:你可以侦听 UserLoggedInEvent 并执行一些操作,例如记录事件、更新用户的上次登录时间戳或向用户发送欢迎消息。
  • 解耦组件:如果你有一个生成通知的组件,你可以定义一个在生成新通知时触发的事件,并让其他组件侦听此事件以执行其他操作。
  • 构建可扩展的系统:你可以定义一组在应用程序中发生某些操作时可以触发的事件,并允许开发人员注册自己的侦听器以执行其他操作。
  • 审计和日志记录:你可以定义一个在每次更新数据库记录时触发的事件,并具有记录更改和进行更改的用户的侦听器。
  • 缓存失效:你可以定义一个在更新用户配置文件时触发的事件,并具有使配置文件的缓存版本无效的侦听器,以便在下次请求时从数据库中检索更新的版本。
  • 与外部系统集成:你可以定义一个在应用程序中放置新订单时触发的事件,并具有通过消息队列或 API 将订单信息发送到外部系统的侦听器。
  • 实施业务规则:你可以定义一个在放置新订单时触发的事件,并具有检查订单是否符合一组业务规则以确定是否可以处理的侦听器。
  • 实时更新:你可以定义一个在博客文章上发布新评论时触发的事件,并具有更新页面以显示新评论的侦听器。
  • 错误处理:你可以定义一个在应用程序中出现错误时触发的事件,并具有记录错误并向支持团队发送电子邮件的侦听器。
  • 功能切换:你可以定义一个在发布新功能时触发的事件,并具有使该功能对某些用户子集启用的侦听器。
  • 用户通知:你可以定义一个在用户的订阅即将到期时触发的事件,并具有向用户发送提醒续订的侦听器。
  • 工作流自动化:你可以定义一个在接收到新订单时触发的事件,并具有自动将订单分配给适当团队进行处理的侦听器。

了解了什么和何时使用之后,让我们来看看如何使用。

如何使用

  • 创建扩展自 ApplicationEvent 的自定义事件。
  • 实现 ApplicationListener 接口定义事件侦听器。
  • 在 Spring 容器中发布事件。

代码

  • 创建你自己的事件
public class CustomizeEvent extends ApplicationEvent {  
  
 private String msg;  
  
 public CustomizeEvent(Object source, String msg) {  
  super(source);  
  this.msg = msg;  
 }  
  
 public String getMsg() {  
  return msg;  
 }  
   
 public void setMsg(String msg) {  
  this.msg = msg;  
 }  
}
  1. 定义侦听器,有两种推荐方式:
  • 注释
  • 编程
// Annotation

@Component  
@Slf4j  
public class AnnotationCustomizeListener {  
   
 @EventListener  
 public void eventListener(CustomizeEvent event){  
  log.info("Annotation Customized Listener catch the Event and the message: {}", event.getMsg());  
 }  
}

// Programmatic 
@Component  
@Slf4j  
public class CustomizeListener implements ApplicationListener<CustomizeEvent> {  
  
 @Override  
 public void onApplicationEvent(CustomizeEvent event) {  
  log.info("CustomizeListener catch the Event and the message: {}", event.getMsg());  
 }  
}
  1. 通过以下方式发布事件:
  • ApplicationContext(它扩展了 ApplicationEventPublisher,因此仍然使用相同的 publishEvent() 方法)
  • ApplicationEventPublisher
//publish when start the application.

@Component  
public class CustomizePublisher {  
 @Autowired  
 ApplicationContext applicationContext;  
   
 public void publish(String msg) {  
  applicationContext.publishEvent(new CustomizeEvent(this, msg));  
 }  
}

//publish when some api been called.

@RestController  
@RequestMapping("/event")  
public class EventController {  
  
 @Autowired private ApplicationEventPublisher publisher;  
 @Autowired private ApplicationContext applicationContext;  
   
 @RequestMapping("/t1")  
 public void sendEvent1() {  
  applicationContext.publishEvent(new CustomizeEvent(new Object(), "Hi applicationContext publish Event"));  
 }  
   
 /**  
 * ApplicationContext extends from ApplicationEventPublisher so it still use same the publishEvent() method  
 */  
   
 @RequestMapping("/t2")  
 public void sendEvent2() {  
  publisher.publishEvent(new CustomizeEvent(new Object(), "Hi ApplicationEventPublisher publish Event"));  
 }  
}

高级用法

  1. 监听器排序:在某些情况下,可能需要确保在其他监听器之前或之后执行某些事件监听器。Spring 提供了 @Order 注释来指定事件侦听器的执行顺序。

默认情况下,按注册顺序执行侦听器。要使用 @Order,只需使用 @Order 注释对事件侦听器方法进行注释并指定一个值。具有最低值的侦听器将首先执行,具有相同值的侦听器将按注册顺序执行。

@Component  
@Slf4j  
public class AnnotationOrderListener {  
   
 @EventListener  
 @Order(2)  
 public void order2(CustomizeEvent event){  
  log.info("CustomizeEvent with order {2}");  
 }  
   
 @EventListener  
 @Order(1)  
 public void order1(CustomizeEvent event){  
  log.info("CustomizeEvent with order {1}");  
 }   
}
  1. 异步监听器Spring 还支持异步事件侦听器。这意味着事件侦听器可以在单独的线程中执行,这可以在某些情况下提高性能。

要使事件侦听器异步,请简单地使用 @Async 注释监听器方法。这将导致该方法在由 Spring 的任务执行器管理的单独线程中执行。

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

@Component  
@Slf4j  
public class AnnotationAsynchronousListener {  

 @EventListener  
 @Async  
 public void AsyncListener(CustomizeEvent event){  
  log.info("CustomizeEvent Asynchronous Listener");  
 }  
}

在使用异步事件侦听器时要小心,因为它们可能会引入复杂性和潜在问题。例如,如果事件侦听器修改共享状态,则必须确保对共享状态的访问是同步的或正确管理的,以避免竞争条件。

此外,如果事件侦听器引发异常,则可能无法被调用线程正确捕获和处理,这可能导致意外的行为。

与所有异步编程一样,重要的是要了解潜在问题并仔细设计你的代码。

最佳实践

  • 将事件与侦听器解耦:将事件定义在与侦听器不同的模块或包中,以确保对一个模块的更改不会影响另一个模块。
  • 对于长时间运行的任务使用异步侦听器:如果侦听器执行耗时任务,请考虑使用异步侦听器以防止主线程阻塞。
  • 在适当的情况下使用事件驱动架构:事件是实现事件驱动架构的好方法,其中服务可以通过事件而不是直接调用进行通信。这可以使你的系统更具可扩展性和灵活性。
  • 使用上下文事件初始化资源:可以使用上下文事件(例如 ContextRefreshedEvent)来初始化应用程序所需的资源,例如数据库连接或缓存。
  • 避免将事件用于关键功能:事件应用于非关键功能,例如日志记录、审计或触发后台任务。避免将事件用于关键功能,例如事务管理或安全检查
  • 使用泛型以确保类型安全:在定义自定义事件时,请使用泛型以确保类型安全并防止类转换异常。
  • 测试你的事件侦听器:编写单元测试以确保事件侦听器按预期工作并正确处理事件。

遵循这些最佳实践,你可以确保使用 Spring 事件是有效、高效和可维护的。总的来说,Spring 事件机制是 Spring 框架非常强大的特性之一,它为 Java 应用程序提供了更灵活、可维护的架构。

译自:https://levelup.gitconnected.com/spring-application-event-mechanism-e6baf1290153

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
阿波
The minute I see you, I want your clothes gone!

评论(0)

添加评论