Spring事件ApplicationEvent源码浅读

2023-10-17 19:36

本文主要是介绍Spring事件ApplicationEvent源码浅读,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • demo应用
    • 实现
    • 基于注解
    • 事件过滤
    • 异步事件监听
  • 源码解读
  • 总结

Spring事件ApplicationEvent源码浅读 - Java技术债务

ApplicationContext 中的事件处理是通过 ApplicationEvent 类和 ApplicationListener 接口提供的。如果将实现了 ApplicationListener 接口的 bean 部署到容器中,则每次将 ApplicationEvent 发布到ApplicationContext 时,都会通知到该 bean,这简直是典型的观察者模式。设计的初衷就是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。

Spring 中提供了以下的事件

Event描述
ContextRefreshedEventApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生
ContextStartedEvent当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序
ContextStoppedEvent当使用 ConfigurableApplicationContext 接口中的 stop() 方法停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作
ContextClosedEvent使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启
RequestHandledEvent这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务
ServletRequestHandledEventRequestHandledEvent的一个子类,用于添加特定于Servlet的上下文信息。

demo应用

具体的详情可以访问:https://cuizb.top/myblog/static/resource/Untitled-1697189238408.png
这里只是个demo例子

实现

1、 自定义事件类,基于ApplicationEvent实现扩展;

public class DemoEvent extends ApplicationEvent {private static final long serialVersionUID = -2753705718295396328L;private String msg;public DemoEvent(Object source, String msg) {super(source);this.msg = msg;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}
}

2、 定义Listener类,实现ApplicationListener接口,并且注入到IOC中等发布者发布事件时,都会通知到这个bean,从而达到监听的效果;

@Component
public class DemoListener implements ApplicationListener<DemoEvent> {@Overridepublic void onApplicationEvent(DemoEvent demoEvent) {String msg = demoEvent.getMsg();System.out.println("bean-listener 收到了 publisher 发布的消息: " + msg);}
}

3、 要发布上述自定义的event,需要调用ApplicationEventPublisher的publishEvent方法,我们可以定义一个实现ApplicationEventPublisherAware的类,并注入IOC来进行调用;

@Component
public class DemoPublisher implements ApplicationEventPublisherAware {private ApplicationEventPublisher applicationEventPublisher;@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {this.applicationEventPublisher = applicationEventPublisher;}public void sendMsg(String msg) {applicationEventPublisher.publishEvent(new DemoEvent(this, msg));}
}

4、 客户端调用publisher;

@RestController
@RequestMapping("/event")
public class DemoClient implements ApplicationContextAware {private ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}@GetMapping("/publish")public void publish(){DemoPublisher bean = applicationContext.getBean(DemoPublisher.class);bean.sendMsg("发布者发送消息......");}
}

输出结果:

bean-listener 收到了 publisher 发布的消息: 发布者发送消息......

基于注解

我们可以不用实现 AppplicationListener 接口 ,在方法上使用 @EventListener 注册事件。如果你的方法应该侦听多个事件,并不使用任何参数来定义,可以在 @EventListener 注解上指定多个事件。

重写DemoListener 类如下:

public class DemoListener {@EventListener(value = {DemoEvent.class, TestEvent.class})public void processApplicationEvent(DemoEvent event) {String msg = event.getMsg();System.out.println("bean-listener 收到了 publisher 发布的消息: " + msg);}
}

事件过滤

如果希望通过一定的条件对事件进行过滤,可以使用 @EventListener 的 condition 属性。以下实例中只有 event 的 msg 属性是 my-event 时才会进行调用。

@EventListener(value = {DemoEvent.class, TestEvent.class}, condition = "#event.msg == 'my-event'")
public void processApplicationEvent(DemoEvent event) {String msg = event.getMsg();System.out.println("bean-listener 收到了 publisher 发布的消息: " + msg);}

此时,发送符合条件的消息,listener 才会侦听到 publisher 发布的消息。

bean-listener 收到了 publisher 发布的消息: my-event

异步事件监听

前面提到的都是同步处理事件,那如果我们希望某个特定的侦听器异步去处理事件,如何做?

使用@Async 注解可以实现类内方法的异步调用,这样方法在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。

@EventListener
@Async
public void processApplicationEvent(DemoEvent event) {String msg = event.getMsg();System.out.println("bean-listener 收到了 publisher 发布的消息: " + msg);
}

使用异步监听时,有两点需要注意:

  • 如果异步事件抛出异常,则不会将其传播到调用方。
  • 异步事件监听方法无法通过返回值来发布后续事件,如果需要作为处理结果发布另一个事件,请插入 ApplicationEventPublisher 以手动发布事件

源码解读

ApplicationEvent事件机制流程:

  1. ApplicationEventPublisher是Spring的事件发布接口,事件源通过该接口的pulishEvent方法发布事件;
  2. ApplicationEventMulticaster就是Spring事件机制中的事件广播器,它默认提供一个SimpleApplicationEventMulticaster实现,如果用户没有自定义广播器,则使用默认的它通过父类AbstractApplicationEventMulticastergetApplicationListeners方法从事件注册表(事件-监听器关系保存)中获取事件监听器,并且通过invokeListener方法执行监听器的具体逻辑;
  3. ApplicationListener就是Spring的事件监听器接口,所有的监听器都实现该接口,本图中列出了典型的几个子类其中RestartApplicationListnenerSpringBoot的启动框架中就有使用;
  4. 在Spring中通常是ApplicationContext本身担任监听器注册表的角色,在其子类AbstractApplicationContext中就聚合了事件广播器ApplicationEventMulticaster和事件监听器ApplicationListnener,并且提供注册监听器的addApplicationListnener方法;

通过上图就能较清晰的知道当一个事件源产生事件时,它通过事件发布器ApplicationEventPublisher发布事件,然后事件广播器ApplicationEventMulticaster会去事件注册表ApplicationContext中找到事件监听器ApplicationListnener,并且逐个执行监听器的onApplicationEvent方法,从而完成事件监听器的逻辑。

来到ApplicationEventPublisher 的 publishEvent 方法内部

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {if (this.earlyApplicationEvents != null) {this.earlyApplicationEvents.add(applicationEvent);}else {getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);}
}

多播事件multicastEvent方法

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));// 注意Executor executor = getTaskExecutor();// 遍历所有的监听者for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {if (executor != null) {// 异步调用监听器executor.execute(() -> invokeListener(listener, event));}else {// 同步调用监听器invokeListener(listener, event);}}
}

在准备执行监听者方法时,会先获取容器中是否有默认的异步线程池,如果在容器启动时,声明了一个异步线程池,getTaskExecutor方法一定不为null,然后异步调用执行listener的业务方法,否则会同步调用执行listener。

此时如果你使用注解@TransactionalEventListener监听,注解会失效。

具体请看:https://cuizb.top/myblog/article/detail/1684739163,

invokeListener方法

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {ErrorHandler errorHandler = getErrorHandler();if (errorHandler != null) {try {doInvokeListener(listener, event);}catch (Throwable err) {errorHandler.handleError(err);}}else {doInvokeListener(listener, event);}
}

doInvokeListener方法

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {try {// 这里是事件发生的地方listener.onApplicationEvent(event);}catch (ClassCastException ex) {......}
}

点击ApplicationListener 接口 onApplicationEvent 方法的实现,可以看到我们重写的方法。

Spring事件ApplicationEvent源码浅读 - Java技术债务

总结

Spring 使用反射机制,获取了所有继承 ApplicationListener 接口的监听器,在 Spring 初始化时,会把监听器都自动注册到注册表中。

Spring 的事件发布非常简单,我们来总结一下:

  1. 定义一个继承ApplicationEvent的事件;
  2. 定义一个实现ApplicationListener的监听器或者使用@EventListener监听事件;
  3. 定义一个发送者,调用ApplicationContext直接发布或者使用ApplicationEventPublisher来发布自定义事件;

最后,发布-订阅模式可以很好的将业务逻辑进行解耦,大大提高了可维护性、可扩展性。

Spring事件ApplicationEvent源码浅读 - Java技术债务


--------------------------------------------------------------欢迎叨扰此地址---------------------------------------------------------------

本文作者:Java技术债务
原文链接:https://cuizb.top/myblog/article/detail/1697189495
版权声明: 本博客所有文章除特别声明外,均采用 CC BY 3.0 CN协议进行许可。转载请署名作者且注明文章出处。

这篇关于Spring事件ApplicationEvent源码浅读的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/227496

相关文章

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环

SpringMVC 通过ajax 前后端数据交互的实现方法

《SpringMVC通过ajax前后端数据交互的实现方法》:本文主要介绍SpringMVC通过ajax前后端数据交互的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价... 在前端的开发过程中,经常在html页面通过AJAX进行前后端数据的交互,SpringMVC的controll