Spring事件发布与监听机制
我是陈皮,一个在互联网 Coding 的 ITer,微信搜索「陈皮的JavaLib」第一时间阅读最新文章,回复【资料】,即可获得我精心整理的技术资料,电子书籍,一线大厂面试资料和优秀简历模板。
前言
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);
}
}
启动服务,会发现在服务启动后,此监听器被触发了。
如果不指定具体的泛型类,则会监听 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);
}
}
注意,监听器类的 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);
}
}
而使用 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);
}
}
其实这是和监听器 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;
}
}
我们可以编写自己的监听器,然后监听这些事件,实现自己的业务逻辑。例如编写 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}
Spring事件发布与监听机制的更多相关文章
- spring 自定义事件发布及监听(简单实例)
前言: Spring的AppilcaitionContext能够发布事件和注册相对应的事件监听器,因此,它有一套完整的事件发布和监听机制. 流程分析: 在一个完整的事件体系中,除了事件和监听器以外,还 ...
- spring中的事件发布与监听
点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. spring事件发布与监听的应用场景 当处理完一段代码逻辑,接下来需要同 ...
- Spring知识点回顾(07)事件发布和监听
Spring知识点回顾(07)事件发布和监听 1.DemoEvent extends ApplicationEvent { public DemoEvent(Object source, String ...
- 关于JAVA中事件分发和监听机制实现的代码实例-绝对原创实用
http://blog.csdn.net/5iasp/article/details/37054171 文章标题:关于JAVA中事件分发和监听机制实现的代码实例 文章地址: http://blog.c ...
- SpringBoot系列——事件发布与监听
前言 日常开发中,我们经常会碰到这样的业务场景:用户注册,注册成功后需要发送邮箱.短信提示用户,通常我们都是这样写: /** * 用户注册 */ @GetMapping("/userRegi ...
- SpringBoot几个重要的事件回调、监听机制
(1).需要配置在META-INF/Spring.factories 1.ApplicationContextInitializer // // Source code recreated from ...
- 深入理解Spring的容器内事件发布监听机制
目录 1. 什么是事件监听机制 2. JDK中对事件监听机制的支持 2.1 基于JDK实现对任务执行结果的监听 3.Spring容器对事件监听机制的支持 3.1 基于Spring实现对任务执行结果的监 ...
- SpringBoot | 第三十二章:事件的发布和监听
前言 今天去官网查看spring boot资料时,在特性中看见了系统的事件及监听章节.想想,spring的事件应该是在3.x版本就发布的功能了,并越来越完善,其为bean和bean之间的消息通信提供了 ...
- 【spring源码学习】spring的事件发布监听机制源码解析
[一]相关源代码类 (1)spring的事件发布监听机制的核心管理类:org.springframework.context.event.SimpleApplicationEventMulticast ...
随机推荐
- 异步阻塞,Manager模块,线程
一.异步阻塞 1.并没有按照执行顺序等待结果 2.而是所有的任务都在异步执行着 3.但是我要的结果又不知道谁的结果先来,谁先结束我就先取谁的结果 很明显的异步,大家都相互执行着(异步过程),谁先结束我 ...
- powercli创建虚拟机步骤及批量创建脚本
https://pubs.vmware.com/vsphere-51/index.jsp?topic=%2Fcom.vmware.powercli.cmdletref.doc%2FSet-OSCust ...
- Keepalived+nginx高可用
这种方法会把Keepalived进程结束掉,在教育机构学习到的方法,我个人对这种方法不认可. 参考: https://www.cnblogs.com/gshelldon/p/14504236.html ...
- Ubuntu 获取升级信息失败。可能网络有问题
升级Ubuntu系统时遇到的这个问题, 原因是默认的源不是国内的,而且还可能导致下载慢等问题. 1.备份源 cd /etc/apt/ sudo cp sources.list sources.list ...
- ceph总结复习
一.ceph概念 Ceph是一种为优秀的性能.可靠性和可扩展性而设计的统一的.分布式文件系统.ceph 的统一体现在可以提供文件系统.块存储和对象存储,分布式体现在可以动态扩展. 什么是块存储/对象存 ...
- python基础之面向对象(一)(概念、实例、魔法方法)
一.面向对象概念理解 1.面向对象和面向过程 面向过程:核心过程二字,过程即解决问题的步骤,就是先干什么后干什么 基于该思想写程序就好比在这是一条流水线,是一种机械式的思维方式 优点:复杂的过程流程化 ...
- linux中级之netfilter防火墙(firewalld)
一.firewalld主要概念 dynamic firewall daemon.支持ipv4和ipv6.Centos7中默认将防火墙从iptables升级为了firewalld.firewalld相对 ...
- 7.json&pickle及软件目录结构规范
json(可以序列化简单数据类型,用于不同语言之间的数据交换传输)import jsonjson.dumps() 写入json.loads() 读取json.dump(info,f) == f.wri ...
- GO学习-(13) Go语言基础之结构体
Go语言基础之结构体 Go语言中没有"类"的概念,也不支持"类"的继承等面向对象的概念.Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性. ...
- GO语言异常处理03---自定义异常
package main import ( "fmt" "time" ) /* type error interface { Error() string } ...