【springboot 事件发布机制源码浅析】

springboot源码分析之事件发布机制浅析

springboot 事件发布机制浅析


文章目录

  • springboot源码分析之事件发布机制浅析
  • 前言
  • 一、自定义事件发布
    • 1.定义事件
    • 2.监听事件
    • 3.发布事件
  • 二、源码分析
    • Listener监听器注册
    • 事件发布与执行
  • 总结


前言

事件发布机制在Spring Boot框架中扮演着重要的角色,它是实现模块解耦、扩展和定制以及自动化配置的关键机制之一。通过事件发布机制,开发者可以实现高度可扩展和可定制的应用程序,并更好地利用Spring Boot框架的各种特性和功能。文章主要包含两个部分,一个是如何使用springboot的事件发布,另一个就是分析其运行原理。

事件发布机制也是使用了观察者模式来实现的,如果你没了解过观察者模式建议先了解一下观察者模式


一、自定义事件发布

1.定义事件

自定义实现类,实现ApplicationEvent类

public class UserUpdateEvent extends ApplicationEvent {public UserUpdateEvent(User updateUser) {super(updateUser);}/*** 获取事件属性* @return*/public User getUser() {return (User)this.source;}
}

2.监听事件

监听事件、执行处理逻辑

方式1:实现ApplicationListener类,需要自己将listener注册到应用中

public class UserUpdateCacheListener implements ApplicationListener<UserUpdateEvent> {@Overridepublic void onApplicationEvent(UserUpdateEvent userUpdateEvent) {User user = userUpdateEvent.getUser();System.out.println(JSON.toJSONString(user));System.out.println("获取到最新用户信息 ----更新缓存");}
}

启动类中注册

    public static void main(String[] args) throws InterruptedException {TypeUtils.compatibleWithJavaBean = true;SpringApplication app =new SpringApplication(DemoApplication.class);app.addListeners(new SpringBootEvnetListenner());//加入自定义的监听类app.addListeners(new UserUpdateCacheListener());//加入自定义的监听类app.addListeners(new UserUpdateDBListener());//加入自定义的监听类app.run(args);}

利用SPI机制注册,在resource下面新建META-INF/spring.factories

org.springframework.context.ApplicationListener=\
com.tfzg.program.core.UserUpdateCacheListener

方式2:直接使用@EventListener标记方法,参数为监听的事件

@Configuration
public class EventListenerConfig {@EventListenerpublic void userListener(UserUpdateEvent event){System.out.println("-------------22----------");System.out.println(event.getUser().getName());}
}

3.发布事件

    @AutowiredApplicationContext applicationContext;@RequestMapping("/pushEvent")public String testPushEvent(){User user = new User();applicationContext.publishEvent(new UserUpdateEvent(user));return "s";}

二、源码分析

主要从两个方面分析springboot的事件发布机制吧,从监听器注册和事件发布执行这两个方向分析springboot事件发布机制原理。

Listener监听器注册

从springboot启动流程,以及bean的生命周期入手,进行分析Listener注册原理

1.1 SpringApplication 构造方法

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));this.webApplicationType = WebApplicationType.deduceFromClasspath();this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));//注册Listener的关键入口就在这儿、通过getSpringFactoriesInstances获取到Listener集合,然后set到SpringApplication 的Listeners 属性中setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = getClassLoader();// Use names and ensure unique to protect against duplicatesSet<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);AnnotationAwareOrderComparator.sort(instances);return instances;}

通过上面的setListeners方法和getSpringFactoriesInstances,可以知道springboot启动的时候,是通过SPI的机制加载spring.factories 文件中配置ApplicationListener,去读取到Listener的,因此我们也可以在spring.factories 中添加自定义Listener或者在SpringApplication 类初始化后直接调用addListeners方法进行添加。

1.2 processBean 阶段会处理有@EventListener注解的方法,对方法解析生成一个ApplicationListenerMethodAdapter Listener


private void processBean(final String beanName, final Class<?> targetType) {if (!this.nonAnnotatedClasses.contains(targetType) &&AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&!isSpringContainerClass(targetType)) {Map<Method, EventListener> annotatedMethods = null;try {annotatedMethods = MethodIntrospector.selectMethods(targetType,(MethodIntrospector.MetadataLookup<EventListener>) method ->AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));}catch (Throwable ex) {// An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.if (logger.isDebugEnabled()) {logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);}}if (CollectionUtils.isEmpty(annotatedMethods)) {this.nonAnnotatedClasses.add(targetType);if (logger.isTraceEnabled()) {logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());}}else {// Non-empty set of methodsConfigurableApplicationContext context = this.applicationContext;Assert.state(context != null, "No ApplicationContext set");List<EventListenerFactory> factories = this.eventListenerFactories;Assert.state(factories != null, "EventListenerFactory List not initialized");for (Method method : annotatedMethods.keySet()) {for (EventListenerFactory factory : factories) {if (factory.supportsMethod(method)) {Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));// 关键代码,包装成了ListenerApplicationListener<?> applicationListener =factory.createApplicationListener(beanName, targetType, methodToUse);if (applicationListener instanceof ApplicationListenerMethodAdapter) {((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);}// 将Listener 加入Listener中context.addApplicationListener(applicationListener);break;}}}if (logger.isDebugEnabled()) {logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +beanName + "': " + annotatedMethods);}}}}

事件发布与执行

通过applicationContext.publishEvent方法入手,进入关键代码AbstractApplicationEventMulticaster.multicastEvent


public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));Executor executor = getTaskExecutor();//过滤Listener,遍历执行监听器方法for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {if (executor != null) {executor.execute(() -> invokeListener(listener, event));}else {//执行 Listener onApplicationEvent 方法invokeListener(listener, event);}}}

根据事件类型,将指定Listener从所有Listener中获取出来,然后再遍历过滤出来的Listener,再遍历执行onApplicationEvent方法。


总结

本篇文章主要展示了springboot事件发布机制的使用,以及运行原理的关键代码。
(如果大家觉得有帮助,帮忙点点赞啦!!!!!!!!!!!!!!!!!!!)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/589218.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据的确权、流通、入表与监管研究(一):数据与确权(下)

关注WX公众号&#xff1a; commindtech77&#xff0c; 获得数据资产相关白皮书下载地址 1. 回复关键字&#xff1a;数据资源入表白皮书 下载 《2023数据资源入表白皮书》 2. 回复关键字&#xff1a;光大银行 下载 光大银行-《商业银行数据资产会计核算研究报告》 3. 回复关键字…

【NLP论文】02 TF-IDF 关键词权值计算

之前写了一篇关于关键词词库构建的文章&#xff0c;没想到反响还不错&#xff0c;最近有空把接下来的两篇补完&#xff0c;也继续使用物流关键词词库举例&#xff0c;本篇文章承接关键词词库构建并以其为基础&#xff0c;将计算各关键词的 TF-IDF 权值&#xff0c;TF-IDF 权值主…

软件工程PPT 笔记摘录(2)

分析软件需求 UML 提供了用例图来分析和描述用例视角的软件需求模型 UML 提供了交互图和状态图来描述行为视角的软件需求模型 UML 提供了类图来描述和分析业务领域的概念模型 顺序图&#xff1a;强调消息传递的时间序 通信图&#xff1a;突出对象间的合作 类图&#xff0…

掌握C++11标准库(STL):理解STL的核心概念

深入探索C11标准库STL&#xff1a;新特性和优化技巧 一、前言二、容器简介三、迭代器简介四、map与unordered_map&#xff08;红黑树VS哈希表&#xff09;4.1、map和unordered_map的差别4.2、优缺点以及适用处4.3、小结 五、总结 一、前言 STL定义了强大的、基于模板的、可复用…

python设计模式:模板方法模式

更多Python学习内容&#xff1a;ipengtao.com 软件设计和编程中&#xff0c;设计模式是一种有助于解决常见问题的强大工具。其中之一是"模板方法模式"&#xff0c;它是一种行为型设计模式&#xff0c;允许你定义一个算法的骨架&#xff0c;但将一些步骤的具体实现延迟…

win11 电脑睡眠功能失效了如何修复 win11 禁止鼠标唤醒

1、win11睡眠不管用怎么办&#xff0c;win11电脑睡眠功能失效了如何修复 在win11系统中拥有许多令人激动的新功能和改进&#xff0c;有些用户在使用win11电脑时可能会遇到一个问题&#xff1a;睡眠模式不起作用。当他们尝试将计算机置于睡眠状态时&#xff0c;却发现系统无法进…

HarmonyOS应用程序包快速修复

快速修复概述 快速修复是HarmonyOS系统提供给开发者的一种技术手段&#xff0c;支持开发者以远快于应用升级的方式对应用程序包进行缺陷修复。和全量应用升级软件版本相比&#xff0c;快速修复的主要优势在小、快和用户体验好。在较短的时间内不中断正在运行的应用的情况下&am…

SpringBoot + Vue 抖音全平台项目

简介 本项目是一个短视频平台&#xff0c;拥有热度排行榜&#xff0c;热门视频&#xff0c;兴趣推送&#xff0c;关注推送&#xff0c;内容审核等功能。 源码下载 网盘 (访问密码: 8418) 登录/注册 首页 创作中心 架构设计 上传视频业务流程 视频推送流程 1.用户订阅分类后…

Day02-ES6

一.proxy代理 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head>…

2023总结

随着各大应用程序开始发送自己的年终总结&#xff0c;我的2023也只剩最后的几个小时了 &#xff0c;我的2023可以说是过的还算顺利&#xff0c;但是也算是一路坎坷&#xff0c;希望2024&#xff0c;我的本命年能够让我过的顺利点。 1&#xff0c;毕业进度 毕业进度总体来说还…

聚类:聚类的介绍及k-means算法

聚类&#xff1a;聚类的介绍及k-means算法 什么是聚类 聚类就是在输入为多个数据时&#xff0c;将“相似”的数据分为一组的操作。1 个组就叫作 1 个 “簇”。下面的示例中每个点都代表1 个数据&#xff0c;在平面上位置较为相近、被圈起来的点就代表一 类相似的数据。也就是…

如何为开源项目和社区做贡献 -- 你应该知道的十件事(一)

1. 前言 大家好&#xff0c;我叫颜国进&#xff0c;现为英特尔边缘计算创新大使、百度飞桨开发者专家。回溯至2021年12月&#xff0c;那时的我&#xff0c;身为机械专业研一新生&#xff0c;仅在C和Python编程语言上有些许基础&#xff0c;对于深度学习的殿堂还只敢在门口窥探&…

【动态规划】【字符串】C++算法:正则表达式匹配

作者推荐 视频算法专题 涉及知识点 动态规划 字符串 LeetCode10:正则表达式匹配 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 ‘.’ 和 ‘’ 的正则表达式匹配。 ‘.’ 匹配任意单个字符 ’ 匹配零个或多个前面的那一个元素 所谓匹配&#xff0c;是…

web网站的工作流程和开发模式

web网站的工作流程和开发模式 基于Java Script封装的高级技术&#xff1a;Vue、Element、Nginx(前端程序部署的服务器) 初识Web前端 Web标准

java8 构建流的5种方法

1 由值创建流&#xff08;Stream.of&#xff09; Stream<String> stream Stream.of("Java 8 ", "Lambdas ", "In ", "Action");stream.map(String::toUpperCase).forEach(System.out::println);Stream<String> emptySt…

Linux常见的21条面试命令

Linux常见的21条面试命令 文章目录 简单文件查看权限文件搜索find() 文件内容&#xff08;查看查找处理&#xff09;catgrepsedpastesortcomm 系统进程内存输入输出 常见的shell 命令循环&#xff0c;判断&#xff0c;变量函数awkgrepsedsortuniqwctr 常见题目词频统计转置文件…

Conv2Former:一种transformer风格的卷积特征提取方式

一、前言 昨天读到了一篇有意思的文章&#xff0c;文章提出通过利用卷积调制操作来简化self-attention。还证明了这种简单的方法可以更好地利用卷积层中嵌套的大核(≥7 7)。我们都知道ViTs推动了设计识别模型的发展&#xff0c;近几年使用的也相当的多&#xff0c;通常就是CN…

《ORANGE’S:一个操作系统的实现》读书笔记(十九)输入输出系统(一)

我们刚刚实现了简单的进程&#xff0c;你现在可能很想把它做得更加完善&#xff0c;比如进一步改进调度算法、增加通信机制等。但是这些工作不但做起来没有尽头&#xff0c;而且有些也是难以实现的&#xff0c;因为进程必须与I/O、内存管理等其它模块一起工作。而且&#xff0c…

R语言【CoordinateCleaner】——cc_coun(): 删除或标记地理坐标与其他国家/地区信息之间的不匹配(通常此信息与样本一起可靠地报告)

Package CoordinateCleaner version 2.0-20 Parameters cc_coun(x,lon "decimallongitude",lat "decimallatitude",iso3 "countrycode",value "clean",ref NULL,ref_col "iso_a3",verbose TRUE ) 参数【x】&#x…

Linux系统---进程程序替换

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C/C》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、进程程序替换 一、替换原理 用fork 创建子进程后执行的是和父进程相同的程序 ( 但有可能执行不同的代码分支 ), 子进程往往要…