springboot监听器模式源码精讲

1.前言

很多时候我们看源码的时候看不下去,其中一个原因是系统往往使用了许多设计模式,如果你不清楚这些设计模式,这无疑增加了你阅读源码的难度。

springboot中就大量使用了设计模式,本文主要介绍其中的一种监听器模式,这是观察者模式中的一种。

作者利用空闲时间去阅读了一下相关的源码,然后决定分享出来,大家相互学习。

这篇文章主要分为3个部分:

第一个部分主要讲下监听模式的几个步骤以及一个简单的例子,这样我们在阅读源码的时候就知道是怎么回事了。

第二个部分是这篇文章的核心,也就是springboot中是如何使用监听模式的。

第三个部分主要是在spingboot中自定义实现监听功能

2.监听模式

实现监听模式的四个步骤:

  • 创建事件
  • 创建事件的监听器
  • 创建广播器
  • 发布事件

我们就按照上面的步骤实现一个监听功能

2.1新建工程

创建一个springboot工程,主要的依赖是这个

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

我们的springboot版本是2.7.17

2.2创建事件

先创建一个基础的模板事件

package com.example.springbootcode.order.event;public abstract class OrderEvent {abstract void desc();
}

这是一个抽象类,里面定义了抽象方法,接下里就创建具体的事件

订单开始创建事件

package com.example.springbootcode.order.event;public class OrderCreateStartingEvent extends OrderEvent{@Overridepublic void desc() {System.out.print("第一步:开始创建订单,……");}
}

订单创建完成事件

package com.example.springbootcode.order.event;public class OrderCreateFinishEvent extends OrderEvent{@Overridepublic void desc() {System.out.print("最后一步:订单创建完成,……");}
}

这里定义了两个事件,都继承了OrderEvent,实现了里面的desc方法

2.3创建监听器

创建一个监听器接口

package com.example.springbootcode.order.event;public interface OrderListener {void onOrderEvent(OrderEvent event);
}

定义了一个监听事件的方法,其参数就是事件,接下来我们实现具体的监听器

订单创建过程监听器

public class OrderLogListener implements OrderListener{@Overridepublic void onOrderEvent(OrderEvent event) {if (event instanceof OrderCreateStartingEvent) {event.desc();} else if (event instanceof OrderCreateFinishEvent){event.desc();}}
}

监听事件,并调用事件里面的desc()方法

2.4创建广播器

定义事件广播器接口

package com.example.springbootcode.order.event;public interface EventMulticaster {void multicasterEvent(OrderEvent event);void addListener(OrderListener listener);void removeListener(OrderListener listener);
}

广播器我们可以这样理解:主要复杂监听器的管理,并将事件广播给所有监听器,如果该监听器对广播的事件感兴趣,那么就会处理事件

接下来实现一下具体的广播器

package com.example.springbootcode.order.event;import java.util.ArrayList;
import java.util.List;public class OrderEventMulticaster implements EventMulticaster{List<OrderListener> listenerList = new ArrayList<>();@Overridepublic void multicasterEvent(OrderEvent event) {listenerList.forEach(listener -> listener.onOrderEvent(event));}@Overridepublic void addListener(OrderListener listener) {listenerList.add(listener);}@Overridepublic void removeListener(OrderListener listener) {listenerList.remove(listener);}
}

这里主要实现了3个方法,事件广播、新增监听器、删除监听器

2.5发布事件

发布事件,其实说白了就是在项目中调用OrderEventMulticaster

@SpringBootApplication
public class SpringbootCodeApplication {public static void main(String[] args) {SpringApplication.run(SpringbootCodeApplication.class, args);OrderEventMulticaster orderEventMulticaster = new OrderEventMulticaster();orderEventMulticaster.addListener(new OrderLogListener());orderEventMulticaster.multicasterEvent(new OrderCreateStartingEvent());}
}

加了两个监听器,然后我们发布了一个订单开始创建事件,控制台会打印"第一步:开始创建订单,……"

到这里为止,一个简单的监听模式就实现了,接下来我们根据这个例子去阅读一下springboot源码中关于监听器的部分。

3.监听模式源码精讲

由于springboot有太多地方使用了监听模式,我们不可能去阅读所有代码,这也不是我写这篇文章的目的。我们只需了解其中的一个事件、一个监听器就知道springboot大概是怎么实现事件的监听和发布。

下面我们以应用程序启动为例,看看其是怎么实现事件的监听和发布。

3.1启动事件

在讲启动事件之前,我们先来看看springboot跟生命周期相关的事件脉络
在这里插入图片描述

由图可以发现,启动程序开始到完成会发布ApplicationStartingEventApplicationPreparedEventApplicationReadyEventApplicationStartedEvent……这些事件。

其实由名字大概就知道它们的意思,这里我们以 ApplicationStartingEvent 这个开始启动事件为例,去看了解其实现过程。

EventObject

是所有事件状态对象的根类, 这是 java.util 包中的 EventObject 类的源代码,里面有个方法 getSource() 用于返回事件最初发生的对象。

ApplicationEvent

提供了一个基本的事件模型,为应用程序中的事件提供了一种通用的表示方式 。

SpringApplicationEvent

提供了一个 与 Spring Boot 应用程序的启动过程相关的事件通用的基类,这里就是类似于我们前面例子中的OrderEvent事件

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;}
}

总得来说就是获取事件的源和事件相关的参数

ApplicationStartingEvent

这个就是启动事件了,类似于我们例子中的OrderCreateStartingEvent,我们看看这个启动事件的代码

public class ApplicationStartingEvent extends SpringApplicationEvent {private final ConfigurableBootstrapContext bootstrapContext;public ApplicationStartingEvent(ConfigurableBootstrapContext bootstrapContext, SpringApplication application,String[] args) {super(application, args);   // Ⅰthis.bootstrapContext = bootstrapContext; // Ⅱ}public ConfigurableBootstrapContext getBootstrapContext() {return this.bootstrapContext;}}

它干了下面这些事:

Ⅰ: 调用父类的构造函数,将 application 设置为事件的源,将参数数组 args 设置为事件的相关参数

**Ⅱ:**初始化一个上下文接口,并通过getBootstrapContext()来获取该对象

总得来说这个事件是比较简单的。

3.2监听器

还是一样,我们先上图,先了解一下关于启动程序事件监听器的实现脉络

在这里插入图片描述

EventListener

这是一个标记接口 ,在 Java 编程中常用于为一组类提供一个共同的标记,而不包含任何方法。标记接口通常用于指示类具有某种特定的性质、行为或能力。

ApplicationListener

这是 Spring Framework 中的 ApplicationListener 接口,用于定义应用程序事件监听器,这里就类似于上面例子中的OrderListener

@FunctionalInterface //Ⅰ
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E event); // Ⅱstatic <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {return event -> consumer.accept(event.getPayload());}}

Ⅰ: @FunctionalInterface 是 Java 注解,用于标记一个接口,指示它是一个函数式接口。函数式接口是只包含一个抽象方法的接口,通常用于支持 Lambda 表达式和函数引用。

Ⅱ:onApplicationEvent(E event)处理事件,其参数是一个泛型,但必须是继承ApplicationEvent

SmartApplicationListenerGenericApplicationListener主要是为了扩展ApplicationListener,这里就不深入讲解了。springboot这样设计的目的其实就是后续的扩展,而不是把所有东西都写在ApplicationListener,基本上所有的系统都是这么个原则。

LoggingApplicationListener

最后我们的主角登场了,从名字大概可以猜到这是一个跟启动日志记录监听器,它会在启动过程的某个阶段记录日志,下面是核心代码

public class LoggingApplicationListener implements GenericApplicationListener {// 省略……@Overridepublic void onApplicationEvent(ApplicationEvent event) {if (event instanceof ApplicationStartingEvent) {onApplicationStartingEvent((ApplicationStartingEvent) event);}else if (event instanceof ApplicationEnvironmentPreparedEvent) {onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);}else if (event instanceof ApplicationPreparedEvent) {onApplicationPreparedEvent((ApplicationPreparedEvent) event);}else if (event instanceof ContextClosedEvent) {onContextClosedEvent((ContextClosedEvent) event);}else if (event instanceof ApplicationFailedEvent) {onApplicationFailedEvent();}}// 省略……
}

从代码中可以看到它是监听启动过程的所有事件,其中第一个就是我们上面讲到的ApplicationStartingEvent开始启动事件,这里类似于我们上面例子中的OrderLogListener

3.3广播器

跟上面一样,我们还是先上图

在这里插入图片描述

ApplicationEventMulticaster

package org.springframework.context.event;import java.util.function.Predicate;import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;public interface ApplicationEventMulticaster {void addApplicationListener(ApplicationListener<?> listener);void addApplicationListenerBean(String listenerBeanName);void removeApplicationListener(ApplicationListener<?> listener);void removeApplicationListenerBean(String listenerBeanName);void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate);void removeApplicationListenerBeans(Predicate<String> predicate);void removeAllListeners();void multicastEvent(ApplicationEvent event);void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);}

定义事件广播器接口,类似于前面例子中的EventMulticaster

AbstractApplicationEventMulticaster

public abstract class AbstractApplicationEventMulticasterimplements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {//省略……@Overridepublic void addApplicationListener(ApplicationListener<?> listener) {synchronized (this.defaultRetriever) {// Explicitly remove target for a proxy, if registered already,// in order to avoid double invocations of the same listener.Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);if (singletonTarget instanceof ApplicationListener) {this.defaultRetriever.applicationListeners.remove(singletonTarget);}this.defaultRetriever.applicationListeners.add(listener);this.retrieverCache.clear();}}//省略……@Overridepublic void removeApplicationListener(ApplicationListener<?> listener) {synchronized (this.defaultRetriever) {this.defaultRetriever.applicationListeners.remove(listener);this.retrieverCache.clear();}}}

这是一个抽象类,实现了监听器的新增和删除的具体逻辑,这样做的目的就是封装一些通用的功能,至于说要不要加一个这样的抽象类完全就是根据你的实际业务情况而定。

SimpleApplicationEventMulticaster

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {//省略……@Overridepublic void multicastEvent(ApplicationEvent event) {multicastEvent(event, resolveDefaultEventType(event));}@Overridepublic 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);}}}//省略……private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {try {listener.onApplicationEvent(event); //广播事件}catch (ClassCastException ex) {String msg = ex.getMessage();if (msg == null || matchesClassCastMessage(msg, event.getClass()) ||(event instanceof PayloadApplicationEvent &&matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))) {// Possibly a lambda-defined listener which we could not resolve the generic event type for// -> let's suppress the exception.Log loggerToUse = this.lazyLogger;if (loggerToUse == null) {loggerToUse = LogFactory.getLog(getClass());this.lazyLogger = loggerToUse;}if (loggerToUse.isTraceEnabled()) {loggerToUse.trace("Non-matching event type for listener: " + listener, ex);}}else {throw ex;}}}
}

这个类继承AbstractApplicationEventMulticaster,并且实现了multicastEvent()广播事件,这类似于例子中的OrderEventMulticaster

3.4发布事件

前面所有东西都准备好了,看看程序启动时是如何监听事件的。我们从启动类开始跟跟踪

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

进入run方法

public class SpringApplication {// 省略代码public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class<?>[] { primarySource }, args);}public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {// Ⅰ:SpringApplication  Ⅱ:runreturn new SpringApplication(primarySources).run(args); }public SpringApplication(Class<?>... primarySources) {this(null, primarySources);}// Ⅲ@SuppressWarnings({ "unchecked", "rawtypes" })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));// ⅣsetListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();}// Ⅴpublic void setListeners(Collection<? extends ApplicationListener<?>> listeners) {this.listeners = new ArrayList<>(listeners);}}

:我们先进入SpringApplication构造函数,最后我们发现会来到Ⅰ->Ⅲ->Ⅳ->Ⅴthis.listeners存储了启动时需要加载的监听器。

我们看看this.listeners存储了哪些监听器

在这里插入图片描述

我们可以发现LoggingApplicationListener监听器也保存在里面,接下来就是怎么把这些监听器加载到广播器中

构造函数实际上时初始化一些变量

还记得上面**Ⅱ:**run这个注释吗,构造函数执行完后,开始执行run方法,我们进入run方法

public ConfigurableApplicationContext run(String... args) {long startTime = System.nanoTime();DefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);// Ⅵlisteners.starting(bootstrapContext, this.mainApplicationClass);// 省略代码return context;
}

**Ⅵ:**这里就是发布一个启动开始事件了,进入starting方法

package org.springframework.boot;
class SpringApplicationRunListeners {// Ⅰvoid starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),(step) -> {if (mainApplicationClass != null) {step.tag("mainApplicationClass", mainApplicationClass.getName());}});}// 省略代码}

这里都是发布启动相关的事件,starting里面调用了doWithListeners方法,其中最主要的是第二个参数,是一个表达式,我们进去看看,最后我们来到这里

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {//省略代码// 前面已经被初始化过public EventPublishingRunListener(SpringApplication application, String[] args) {this.application = application;this.args = args;// Ⅰ new广播器this.initialMulticaster = new SimpleApplicationEventMulticaster();for (ApplicationListener<?> listener : application.getListeners()) {// Ⅱ 添加监听器,application.getListeners()获取的就是前面的this.listenersthis.initialMulticaster.addApplicationListener(listener);}}//省略代码@Overridepublic void starting(ConfigurableBootstrapContext bootstrapContext) {this.initialMulticaster// Ⅲ 广播事件.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));}//省略代码
}

这里的步骤就像我们上面例子中的一样

@SpringBootApplication
public class SpringbootCodeApplication {public static void main(String[] args) {SpringApplication.run(SpringbootCodeApplication.class, args);OrderEventMulticaster orderEventMulticaster = new OrderEventMulticaster();orderEventMulticaster.addListener(new OrderLogListener());orderEventMulticaster.multicasterEvent(new OrderCreateStartingEvent());}
}

new一个广播器,然后添加监听器,最后是广播事件。

4.自定义监听器

了解了spingboot的监听模式后,接下来我们看看如何在springboot项目中使用它。

4.1创建事件

package com.example.springbootcode.my;import org.springframework.context.ApplicationEvent;public class UserMsgEvent extends ApplicationEvent {public UserMsgEvent(Object source) {super(source);}// 自定义自己的逻辑方法public void sendMsg(){System.out.print("向用户发送消息");}
}

这里需要注意的是我们继承的是ApplicationEvent,不继承SpringApplicationEvent主要是因此其跟启动相关的,我们定义的事件明显跟应用启动没啥关系。

4.2创建监听器

package com.example.springbootcode.my;import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;@Component
public class UserListener implements ApplicationListener<UserMsgEvent> {@Overridepublic void onApplicationEvent(UserMsgEvent event) {event.sendMsg();}
}

这里继承ApplicationListener

4.3自定义触发事件

package com.example.springbootcode.my;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;@Service
public class UserService {private final ApplicationEventPublisher eventPublisher;public UserService(ApplicationEventPublisher eventPublisher) {this.eventPublisher = eventPublisher;}public void send(){eventPublisher.publishEvent(new UserMsgEvent(this));}
}

通过构造函数注入 ApplicationEventPublisher , 这是一个Spring框架提供的接口,用于发布事件

最后在需要调用的地方调用UserService里面的send方法即可

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

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

相关文章

数学建模-基于BL回归模型和决策树模型对早产危险因素的探究和预测

整体求解过程概述(摘要) 近年来&#xff0c;全球早产率总体呈上升趋势&#xff0c;在我国&#xff0c;早产儿以每年 20 万的数目逐年递增&#xff0c;目前早产已经成为重大的公共卫生问题之一。据研究,早产是威胁胎儿及新生儿健康的重要因素&#xff0c;可能会造成死亡或智力体…

深度学习 -- 神经网络

1、神经网络的历史 2、 M-P模型 M-P模型是首个通过模仿神经元而形成的模型。在M-P模型中,多个输入节点对应一个输出节点y。每个输入x,乘以相应的连接权重w,然后相加得到输出y。结果之和如果大于阈值h,则输出1,否则输出0。输入和输出均是0或1。 公式2.1&#xff1a; …

Redis 安装部署

文章目录 1、前言2、安装部署2.1、单机模式2.1.1、通过 yum 安装&#xff08;不推荐&#xff0c;版本老旧&#xff09;2.1.1、通过源码编译安装&#xff08;推荐&#xff09; 2.2、主从模式2.3、哨兵模式2.4、集群模式2.5、其他命令2.6、其他操作系统 3、使用3.1、Java 代码 —…

神经网络中的 Grad-CAM 热图(Gradient-weighted Class Activation Mapping)

Grad-CAM&#xff08;Gradient-weighted Class Activation Mapping&#xff09;是一种用于可视化卷积神经网络&#xff08;CNN&#xff09;中特定类别的激活区域的方法。其基本思想是使用网络的梯度信息来获取关于特定类别的空间定位信息。 Grad-CAM 的具体公式如下&#xff1…

Python逐步打造惊艳的折线图

大家好&#xff0c;Matplotlib可以快速轻松地使用现成的函数绘制图表&#xff0c;但是微调步骤需要花费更多精力。今天本文将介绍如何使用Matplotlib绘制吸引人的图表&#xff0c;实现折线图的惊艳变身。 1.数据 为了说明方法&#xff0c;本文使用了包含过去50年各国GDP信息的…

QT 中 QTimer 类 备查

基础 // 指定了父对象, 创建的堆内存可以自动析构 QTimer::QTimer(QObject *parent nullptr);// 根据指定的时间间隔启动或者重启定时器, 需要调用 setInterval() 设置时间间隔 void QTimer::start();// 启动或重新启动定时器&#xff0c;超时间隔为msec毫秒。 void QTimer::…

韩语语法中에和로/으로区别,柯桥发音入门韩语培训学校

에和로/으로在行动的去向与到达或涉及的地点一致时&#xff0c;二者可以互换。 但是에表示到达或涉及的具体地点&#xff0c;而로/으로表示的时动作指向的方向或经过的地点。 在只表示去向而不表示具体地点时&#xff0c;只能用로/으로&#xff0c;而在只表示具体地点而不表示方…

2023.12.2 做一个后台管理网页(左侧边栏实现手风琴和隐藏/出现效果)

2023.12.2 做一个后台管理网页&#xff08;左侧边栏实现手风琴和隐藏/出现效果&#xff09; 网页源码见附件&#xff0c;比较简单&#xff0c;之前用很多种方法实现过该效果&#xff0c;这次的效果相对更好。 实现功能&#xff1a; &#xff08;1&#xff09;实现左侧边栏的手…

摩根士丹利:人工智能推动增长

摩根士丹利&#xff08;NYSE&#xff1a;MS&#xff09;将人工智能战略整合到其财富管理业务中&#xff0c;标志着竞争性金融格局迈出了变革性的一步。该公司的人工智能计划&#xff0c;包括与 OpenAI 合作开发人工智能聊天机器人&#xff0c;促进了其财富部门的显着增长。值得…

【数据库】数据库基于封锁机制的调度器,使冲突可串行化,保障事务和调度一致性

封锁使可串行化 ​专栏内容&#xff1a; 手写数据库toadb 本专栏主要介绍如何从零开发&#xff0c;开发的步骤&#xff0c;以及开发过程中的涉及的原理&#xff0c;遇到的问题等&#xff0c;让大家能跟上并且可以一起开发&#xff0c;让每个需要的人成为参与者。 本专栏会定期更…

Linux查看计算机处理器相关的信息

采用命令lscpu。部分结果如下&#xff1a;

MicroPython标准库

MicroPython标准库 arraybinascii(二进制/ASCII转换)builtins – 内置函数和异常cmath – 复数的数学函数collections – 集合和容器类型errno – 系统错误代码gc – 控制垃圾收集器hashlib – 散列算法heapq – 堆队列算法io – 输入/输出流json – JSON 编码和解码math – 数…

详解Spring中BeanPostProcessor在Spring工厂和Aop发挥的作用

&#x1f609;&#x1f609; 学习交流群&#xff1a; ✅✅1&#xff1a;这是孙哥suns给大家的福利&#xff01; ✨✨2&#xff1a;我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 &#x1f96d;&#x1f96d;3&#xff1a;QQ群&#xff1a;583783…

【开源】基于Vue+SpringBoot的音乐平台

项目编号&#xff1a; S 055 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S055&#xff0c;文末获取源码。} 项目编号&#xff1a;S055&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示 四、核心代码4.1 查询单首…

Vue安装及环境配置详细教程

一、下载node.js 访问node.js官网&#xff1a;Download | Node.js 选择Windows Installer (.msi)的64-bit进行下载。 在E盘新建一个文件夹&#xff0c;取名为nodejs&#xff0c;也可以在其他盘符新建。 在安装node.js时&#xff0c;点击Change...&#xff0c;进行切换盘符安…

使用 STM32 微控制器读取光电传感器数据的实现方法

本文介绍了如何使用 STM32 微控制器读取光电传感器数据的实现方法。通过配置和使用STM32的GPIO和ADC功能&#xff0c;可以实时读取光电传感器的模拟信号并进行数字化处理。本文将介绍硬件连接和配置&#xff0c;以及示例代码&#xff0c;帮助开发者完成光电传感器数据的读取。 …

<JavaEE> 什么是线程安全?产生线程不安全的原因和处理方式

目录 一、线程安全的概念 二、线程不安全经典示例 三、线程不安全的原因和处理方式 3.1 线程的随机调度和抢占式执行 3.2 修改共享数据 3.3 关键代码或指令不是“原子”的 3.4 内存可见性和指令重排序 四、Java标准库自带的线程安全类 一、线程安全的概念 线程安全是指…

无人机助力电力设备螺母缺销智能检测识别,python基于YOLOv7开发构建电力设备螺母缺销小目标检测识别系统

传统作业场景下电力设备的运维和维护都是人工来完成的&#xff0c;随着现代技术科技手段的不断发展&#xff0c;基于无人机航拍飞行的自动智能化电力设备问题检测成为了一种可行的手段&#xff0c;本文的核心内容就是基于YOLOv7来开发构建电力设备螺母缺销检测识别系统&#xf…

spark的安装与使用:一键自动安装

使用shell脚本一键下载、安装、配置spark&#xff08;单机版&#xff09; 1. 把下面的脚本复制保存为/tmp/install_spark.sh文件 #!/bin/bash# sprak安装版本 sprak_version"2.4.0" # sprak安装目录 sprak_installDir"/opt/module" # hadoop安装路径&…

【数组和函数实战: 斗地主游戏】

目录 1. 玩法说明 2. 分析和设计 3. 代码实现 4. 游戏演示1. 玩法说明 一副54张牌,3最小,两个王最大,其实是2,和上面一样从大到小排列 2. 分析和设计 2.1 分析和设计 常量和变量设计 一副牌有54张,有牌的数值和花色,可以分别用两个数组来存储,card为卡牌表示的数值,color为…