解析Spring事件发布与监听机制

作者:陈皮的JavaLib 时间:2022-09-01 09:52:19 

前言

Spring 提供了 ApplicationContext 事件机制,可以发布和监听事件,这个特性非常有用。

Spring 内置了一些事件和 * ,例如在 Spring 容器启动前,Spring 容器启动后,应用启动失败后等事件发生后,监听在这些事件上的 * 会做出相应的响应处理。

当然,我们也可以自定义 * ,监听 Spring 原有的事件。或者自定义我们自己的事件和 * ,在必要的时间点发布事件,然后 * 监听到事件就做出响应处理。

ApplicationContext 事件机制

ApplicationContext 事件机制采用观察者设计模式来实现,通过 ApplicationEvent 事件类和 ApplicationListener * 接口,可以实现 ApplicationContext 事件发布与处理。

每当 ApplicationContext 发布 ApplicationEvent 时,如果 Spring 容器中有 ApplicationListener bean,则 * 会被触发执行相应的处理。当然,ApplicationEvent 事件的发布需要显示触发,要么 Spring 显示触发,要么我们显示触发。

ApplicationListener *

定义应用 * 需要实现的接口。此接口继承了 JDK 标准的事件 * 接口 EventListener,EventListener 接口是一个空的标记接口,推荐所有事件 * 必须要继承它。


package org.springframework.context;

import java.util.EventListener;

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

/**
* 处理应用事件
*/
void onApplicationEvent(E event);
}

package java.util;

public interface EventListener {
}

ApplicationListener 是个泛型接口,我们自定义此接口的实现类时,如果指定了泛型的具体事件类,那么只会监听此事件。如果不指定具体的泛型,则会监听 ApplicationEvent 抽象类的所有子类事件。

如下我们定义一个 * ,监听具体的事件,例如监听 ApplicationStartedEvent 事件。


package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
* @Description
* @Author 陈皮
* @Date 2021/6/26
* @Version 1.0
*/
@Slf4j
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationStartedEvent> {
   @Override
   public void onApplicationEvent(ApplicationStartedEvent event) {
       log.info(">>> MyApplicationListener:{}", event);
   }
}

启动服务,会发现在服务启动后,此 * 被触发了。

解析Spring事件发布与监听机制

如果不指定具体的泛型类,则会监听 ApplicationEvent 抽象类的所有子类事件。如下所示:


package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
* @Description
* @Author 陈皮
* @Date 2021/6/26
* @Version 1.0
*/
@Slf4j
@Component
public class MyApplicationListener implements ApplicationListener {
   @Override
   public void onApplicationEvent(ApplicationEvent event) {
       log.info(">>> MyApplicationListener:{}", event);
   }
}

解析Spring事件发布与监听机制

注意, * 类的 bean 要注入到 Spring 容器中,不然不会生效。一种是使用注解注入,例如 @Component。另外可以使用 SpringApplicationBuilder.listeners() 方法添加,不过这两种方式有区别的,看以下示例。

首先我们使用 @Component 注解方式,服务启动时,监视到了2个事件:

  • ApplicationStartedEvent

  • ApplicationReadyEvent


package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.SpringApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
* @Description
* @Author 陈皮
* @Date 2021/6/26
* @Version 1.0
*/
@Slf4j
@Component
public class MyApplicationListener implements ApplicationListener<SpringApplicationEvent> {
   @Override
   public void onApplicationEvent(SpringApplicationEvent event) {
       log.info(">>> MyApplicationListener:{}", event);
   }
}

解析Spring事件发布与监听机制

而使用 SpringApplicationBuilder.listeners() 方法添加 * ,服务启动时,监听到了5个事件:

  • ApplicationEnvironmentPreparedEvent

  • ApplicationContextInitializedEvent

  • ApplicationPreparedEvent

  • ApplicationStartedEvent

  • ApplicationReadyEvent


package com.chenpi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Application {

public static void main(String[] args) {
       // SpringApplication.run(Application.class, args);

SpringApplication app = new SpringApplicationBuilder(Application.class)
               .listeners(new MyApplicationListener()).build();
       app.run(args);
   }
}

解析Spring事件发布与监听机制

其实这是和 * bean 注册的时机有关,@component 注解的 * bean 只有在 bean 初始化注册完后才能使用;而通过 SpringApplicationBuilder.listeners() 添加的 * bean 是在容器启动前,所以监听到的事件比较多。但是注意,这两个不要同时使用,不然 * 会重复执行两遍。

如果你想在 * bean 中注入其他 bean(例如 @Autowired),那最好是使用注解形式,因为如果太早发布 * ,可能其他 bean 还未初始化完成,可能会报错。

ApplicationEvent 事件


ApplicationEvent 是所有应用事件需要继承的抽象类。它继承了 EventObject 类,EventObject 是所有事件的根类,这个类有个 Object 类型的对象 source,代表事件源。所有继承它的类的构造函数都必须要显示传递这个事件源。


package org.springframework.context;

import java.util.EventObject;

public abstract class ApplicationEvent extends EventObject {

private static final long serialVersionUID = 7099057708183571937L;

// 发布事件的系统时间
private final long timestamp;

public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}

public final long getTimestamp() {
return this.timestamp;
}
}

package java.util;

public class EventObject implements java.io.Serializable {

private static final long serialVersionUID = 5516075349620653480L;

protected transient Object  source;

public EventObject(Object source) {
       if (source == null)
           throw new IllegalArgumentException("null source");

this.source = source;
   }

public Object getSource() {
       return source;
   }

public String toString() {
       return getClass().getName() + "[source=" + source + "]";
   }
}

在 Spring 中,比较重要的事件类是 SpringApplicationEvent。Spring 有一些内置的事件,当完成某种操作时会触发某些事件。这些内置事件继承 SpringApplicationEvent 抽象类。SpringApplicationEvent 继承 ApplicationEvent 并增加了字符串数组参数字段 args。


/**
* Base class for {@link ApplicationEvent} related to a {@link SpringApplication}.
*
* @author Phillip Webb
* @since 1.0.0
*/
@SuppressWarnings("serial")
public abstract class SpringApplicationEvent extends ApplicationEvent {

private final String[] args;

public SpringApplicationEvent(SpringApplication application, String[] args) {
super(application);
this.args = args;
}

public SpringApplication getSpringApplication() {
return (SpringApplication) getSource();
}

public final String[] getArgs() {
return this.args;
}
}

解析Spring事件发布与监听机制

我们可以编写自己的 * ,然后监听这些事件,实现自己的业务逻辑。例如编写 ApplicationListener 接口的实现类,监听 ContextStartedEvent 事件,当应用容器 ApplicationContext 启动时,会发布该事件,所以我们编写的 * 会被触发。

  • ContextRefreshedEvent:ApplicationContext 被初始化或刷新时,事件被发布。ConfigurableApplicationContext接口中的 refresh() 方法被调用也会触发事件发布。初始化是指所有的 Bean 被成功装载,后处理 Bean 被检测并激活,所有单例 Bean 被预实例化,ApplicationContext 容器已就绪可用。

  • ContextStartedEvent:应用程序上下文被刷新后,但在任何 ApplicationRunner 和 CommandLineRunner 被调用之前,发布此事件。

  • ApplicationReadyEvent:此事件会尽可能晚地被发布,以表明应用程序已准备好为请求提供服务。事件源是SpringApplication 本身,但是要注意修改它的内部状态,因为到那时所有初始化步骤都已经完成了。

  • ContextStoppedEvent:ConfigurableApplicationContext 接口的 stop() 被调用停止 ApplicationContext 时,事件被发布。

  • ContextClosedEvent:ConfigurableApplicationContext 接口的 close() 被调用关闭 ApplicationContext 时,事件被发布。注意,一个已关闭的上下文到达生命周期末端后,它不能被刷新或重启。

  • ApplicationFailedEvent:当应用启动失败后发布事件。

  • ApplicationEnvironmentPreparedEvent:事件是在 SpringApplication 启动时发布的,并且首次检查和修改 Environment 时,此时上 ApplicationContext 还没有创建。

  • ApplicationPreparedEvent:事件发布时,SpringApplication 正在启动,ApplicationContext 已经完全准备好,但没有刷新。在这个阶段,将加载 bean definitions 并准备使用 Environment。

  • RequestHandledEvent:这是一个 web 事件,只能应用于使用 DispatcherServlet 的 Web 应用。在使用 Spring 作为前端的 MVC 控制器时,当 Spring 处理用户请求结束后,系统会自动触发该事件。

自定义事件和 *


前面介绍了自定义 * ,然后监听 Spring 原有的事件。下面介绍自定义事件和自定义 * ,然后在程序中发布事件,触发 * 执行,实现自己的业务逻辑。

首先自定义事件,继承 ApplicationEvent,当然事件可以自定义自己的属性。


package com.chenpi;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.context.ApplicationEvent;

/**
* @Description 自定义事件
* @Author 陈皮
* @Date 2021/6/26
* @Version 1.0
*/
@Getter
@Setter
public class MyApplicationEvent extends ApplicationEvent {

// 事件可以增加自己的属性
   private String myField;

public MyApplicationEvent(Object source, String myField) {
       // 绑定事件源
       super(source);
       this.myField = myField;
   }

@Override
   public String toString() {
       return "MyApplicationEvent{" + "myField='" + myField + '\'' + ", source=" + source + '}';
   }
}

然后自定义 * ,监听我们自定义的事件。


package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;

/**
* @Description 自定义 *
* @Author 陈皮
* @Date 2021/6/26
* @Version 1.0
*/
@Slf4j
public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> {
   @Override
   public void onApplicationEvent(MyApplicationEvent event) {
       log.info(">>> MyApplicationListener:{}", event);
   }
}

注册 * 和发布事件。注册 * 上面讲解了有两种方式。事件的发布可以通过 ApplicationEventPublisher.publishEvent() 方法。此处演示直接用 configurableApplicationContext 发布,它实现了 ApplicationEventPublisher 接口。


package com.chenpi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Application {

public static void main(String[] args) {
       // SpringApplication.run(Application.class, args);
       // 注册 *
       SpringApplication app = new SpringApplicationBuilder(Application.class)
               .listeners(new MyApplicationListener()).build();
       ConfigurableApplicationContext configurableApplicationContext = app.run(args);
       // 方便演示,在项目启动后发布事件,当然也可以在其他操作和其他时间点发布事件
       configurableApplicationContext
               .publishEvent(new MyApplicationEvent("我是事件源,项目启动成功后发布事件", "我是自定义事件属性"));
   }
}

启动服务,结果显示确实监听到发布的事件了。


2021-06-26 16:15:09.584  INFO 10992 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8081 (http) with context path ''
2021-06-26 16:15:09.601  INFO 10992 --- [           main] com.chenpi.Application                   : Started Application in 2.563 seconds (JVM running for 4.012)
2021-06-26 16:15:09.606  INFO 10992 --- [           main] com.chenpi.MyApplicationListener         : >>> MyApplicationListener:MyApplicationEvent{myField='我是自定义事件属性', source=我是事件源,项目启动成功后发布事件
}

事件监听机制能达到分发,解耦效果。例如可以在业务类中发布事件,让监听在此事件的 * 执行自己的业务处理。例如:


package com.chenpi;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

/**
* @Description
* @Author 陈皮
* @Date 2021/6/26
* @Version 1.0
*/
@Service
public class MyService implements ApplicationEventPublisherAware {

private ApplicationEventPublisher applicationEventPublisher;

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

public void testEvent() {
       applicationEventPublisher
               .publishEvent(new MyApplicationEvent("我是事件源", "我是自定义事件属性"));
   }
}

注解式 *


除了实现 ApplicationListener 接口创建 * 外,Spring 还提供了注解 @EventListener 来创建 * 。


package com.chenpi;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
* @Description 自定义 *
* @Author 陈皮
* @Date 2021/6/26
* @Version 1.0
*/
@Slf4j
@Component
public class MyApplicationListener01 {
   @EventListener
   public void onApplicationEvent(MyApplicationEvent event) {
       log.info(">>> MyApplicationListener:{}", event);
   }
}

而且注解还可以通过条件过滤只监听指定条件的事件。例如事件的 myField 属性的值等于"陈皮"的事件。


package com.chenpi;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
* @Description 自定义 *
* @Author 陈皮
* @Date 2021/6/26
* @Version 1.0
*/
@Slf4j
@Component
public class MyApplicationListener01 {
   @EventListener(condition = "#event.myField.equals('陈皮')")
   public void onApplicationEvent(MyApplicationEvent event) {
       log.info(">>> MyApplicationListener:{}", event);
   }
}

还可以在同一个类中定义多个监听,对同一个事件的不同监听还可以指定顺序。order 值越小越先执行。


package com.chenpi;

import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
* @Description 自定义 *
* @Author 陈皮
* @Date 2021/6/26
* @Version 1.0
*/
@Slf4j
@Component
public class MyApplicationListener01 {
   @Order(2)
   @EventListener
   public void onApplicationEvent(MyApplicationEvent event) {
       log.info(">>> onApplicationEvent order=2:{}", event);
   }

@Order(1)
   @EventListener
   public void onApplicationEvent01(MyApplicationEvent event) {
       log.info(">>> onApplicationEvent order=1:{}", event);
   }

@EventListener
   public void otherEvent(YourApplicationEvent event) {
       log.info(">>> otherEvent:{}", event);
   }
}

执行结果如下:

>>> onApplicationEvent order=1:MyApplicationEvent{myField='陈皮', source=我是事件源}
>>> onApplicationEvent order=2:MyApplicationEvent{myField='陈皮', source=我是事件源}
>>> otherEvent:MyApplicationEvent{myField='我是自定义事件属性01', source=我是事件源01}

事件的监听处理是同步的,如下:


package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

/**
* @Description
* @Author 陈皮
* @Date 2021/6/26
* @Version 1.0
*/
@Service
@Slf4j
public class MyService implements ApplicationEventPublisherAware {

private ApplicationEventPublisher applicationEventPublisher;

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

public void testEvent() {
       log.info(">>> testEvent begin");
       applicationEventPublisher.publishEvent(new MyApplicationEvent("我是事件源", "陈皮"));
       applicationEventPublisher.publishEvent(new YourApplicationEvent("我是事件源01", "我是自定义事件属性01"));
       log.info(">>> testEvent end");
   }
}

执行结果如下:

2021-06-26 20:34:27.990 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyService : >>> testEvent begin
2021-06-26 20:34:27.990 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01 : >>> onApplicationEvent order=1:MyApplicationEvent{myField='陈皮', source=我是事件源}
2021-06-26 20:34:27.991 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01 : >>> onApplicationEvent order=2:MyApplicationEvent{myField='陈皮', source=我是事件源}
2021-06-26 20:34:27.992 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01 : >>> otherEvent:MyApplicationEvent{myField='我是自定义事件属性01', source=我是事件源01}
2021-06-26 20:34:27.992 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyService : >>> testEvent end

不过,我们也可以显示指定异步方式去执行 * ,记得在服务添加 @EnableAsync 注解开启异步注解。


package com.chenpi;

import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
* @Description 自定义 *
* @Author 陈皮
* @Date 2021/6/26
* @Version 1.0
*/
@Slf4j
@Component
public class MyApplicationListener01 {

@Async
   @Order(2)
   @EventListener
   public void onApplicationEvent(MyApplicationEvent event) {
       log.info(">>> onApplicationEvent order=2:{}", event);
   }

@Order(1)
   @EventListener
   public void onApplicationEvent01(MyApplicationEvent event) {
       log.info(">>> onApplicationEvent order=1:{}", event);
   }

@Async
   @EventListener
   public void otherEvent(YourApplicationEvent event) {
       log.info(">>> otherEvent:{}", event);
   }
}

执行结果如下,注意打印的线程名。


2021-06-26 20:37:04.807  INFO 9092 --- [nio-8081-exec-1] com.chenpi.MyService                     : >>> testEvent begin
2021-06-26 20:37:04.819  INFO 9092 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01       : >>> onApplicationEvent order=1:MyApplicationEvent{myField='陈皮', source=我是事件源}
2021-06-26 20:37:04.831  INFO 9092 --- [         task-1] com.chenpi.MyApplicationListener01       : >>> onApplicationEvent order=2:MyApplicationEvent{myField='陈皮', source=我是事件源}
2021-06-26 20:37:04.831  INFO 9092 --- [nio-8081-exec-1] com.chenpi.MyService                     : >>> testEvent end
2021-06-26 20:37:04.831  INFO 9092 --- [         task-2] com.chenpi.MyApplicationListener01       : >>> otherEvent:MyApplicationEvent{myField='我是自定义事件属性01', source=我是事件源01
}

来源:https://blog.csdn.net/chenlixiao007/article/details/118256425

标签:Spring,事件,监听,机制
0
投稿

猜你喜欢

  • SpringBoot集成RabbitMQ的方法(死信队列)

    2023-06-10 15:12:06
  • Spring Boot启动时调用自己的非web逻辑

    2022-02-15 11:40:10
  • 深入浅析Java反射机制

    2023-11-25 07:02:03
  • java和javascript中过滤掉img形式的字符串不显示图片的方法

    2021-08-31 10:12:49
  • Java实例讲解注解的应用

    2021-11-02 01:43:14
  • java面试题之try中含return语句时代码的执行顺序详解

    2023-11-24 07:34:16
  • startActivityForResult和setResult案例详解

    2023-09-15 19:13:33
  • 记一次springboot服务凌晨无故宕机问题的解决

    2023-07-25 04:50:23
  • Java中的SuppressWarnings注解使用

    2023-08-18 17:31:19
  • Spring Cloud Gateway网关XSS过滤方式

    2021-08-07 13:16:53
  • Java日常练习题,每天进步一点点(33)

    2023-09-22 05:32:41
  • Java日常练习题,每天进步一点点(58)

    2021-06-26 01:13:02
  • C# DataGridView添加新行的2个方法

    2023-06-23 05:42:19
  • WinForm实现为TextBox设置水印文字功能

    2023-06-09 21:15:38
  • java实例方法被覆盖,静态方法被隐藏Explain(详解)

    2022-07-20 08:05:03
  • 浅谈java中Math.random()与java.util.random()的区别

    2023-11-26 16:37:16
  • 详解Mybatis极其(最)简(好)单(用)的一个分页插件

    2021-09-25 03:00:35
  • Flutter控制组件显示和隐藏三种方式详解

    2023-07-07 03:03:30
  • springboot 集成redission 以及分布式锁的使用详解

    2023-06-20 06:48:43
  • 使用C++ Matlab中的lp2lp函数教程详解

    2023-07-13 17:13:12
  • asp之家 软件编程 m.aspxhome.com