当前位置: 首页 > news >正文

@EventListener注解详细使用(IT枫斗者)

@EventListener注解详细使用

简介

  • @EventListener是一种事件驱动编程在spring4.2的时候开始有的,早期可以实现ApplicationListener接口, 为我们提供的一个事件监听、订阅的实现,内部实现原理是观察者设计模式;为的就是业务系统逻辑的解耦,提高可扩展性以及可维护性。事件发布者并不需要考虑谁去监听,监听具体的实现内容是什么,发布者的工作只是为了发布事件而已。
  • 比如我们做一个电商系统,用户下单支付成功后,我们一般要发短信或者邮箱给用户提示什么的,这时候就可以把这个通知业务做成一个单独事件监听,等待通知就可以了;把它解耦处理。

使用@EventListener注解

  • 建立事件对象,当调用publishEvent方法是会通过这个bean对象找对应事件的监听。AddDataEvent.java

  • package com.rw.article.pay.event.bean;import org.springframework.context.ApplicationEvent;/*** 新增mongodb数据事件*/
    public class AddDataEvent extends ApplicationEvent {public AddDataEvent(Object source) {super(source);}public AddDataEvent(Object source, Class clz, Object data) {super(source);this.clz = clz;this.data = data;}public AddDataEvent(Object source, Class clz, Object data, String modelName, String userAgent) {super(source);this.clz = clz;this.data = data;this.modelName = modelName;this.userAgent = userAgent;}/** 要更新的表对象 **/private Class clz;/** 操作的数据**/private Object data;/** 模块名称**/private String modelName;/** 浏览器标识 **/private String userAgent;public Class getClz() {return clz;}public void setClz(Class clz) {this.clz = clz;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}public String getModelName() {return modelName;}public void setModelName(String modelName) {this.modelName = modelName;}public String getUserAgent() {return userAgent;}public void setUserAgent(String userAgent) {this.userAgent = userAgent;}
    }
  • 对应的监听AddDataEventListener .java

  • package com.rw.article.pay.event.listener;
    import com.alibaba.fastjson.JSON;
    import com.rw.article.pay.event.bean.AddDataEvent;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;import org.springframework.context.event.EventListener;
    import org.springframework.stereotype.Component;/*** 新增数据的事件监听*/
    @Component
    public class AddDataEventListener {private static Logger log = LoggerFactory.getLogger(AddDataEventListener.class);/** 在AnnotationConfigUtils#registerAnnotationConfigProcessors注册了BeanDefinition 对应的是EventListenerMethodProcessor对象   , AnnotationConfigUtils在AnnotationConfigServletWebServerApplicationContext构造方法里被加载* *//*** DefaultListableBeanFactory#中preInstantiateSingletons -> (beanName为org.springframework.context.event.internalEventListenerProcessor时得到EventListenerMethodProcessor)EventListenerMethodProcessor#afterSingletonsInstantiated this.processBean(factories, beanName, type)* 然后把要执行的方法封装为ApplicationListenerMethodAdapter -> 添加到listener中 AbstractApplicationEventMulticaster#addApplicationListener* */// 该方法在 ApplicationListenerMethodAdapter 利用反射执行/*** 处理新增数据的事件**/@EventListenerpublic void handleAddEvent(AddDataEvent event) {log.info("发布的data为:{}  ", JSON.toJSONString(event));}
    }
    
  • 建立测试类

  • package com.rw.article.pay.action;import com.rw.article.pay.event.bean.AddDataEvent;
    import org.springframework.context.ApplicationContext;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;import javax.annotation.Resource;/*** 测试的controller*/
    @Controller
    @RequestMapping("/test")
    public class TestController {@Resourceprivate ApplicationContext applicationContext;@ResponseBody@RequestMapping("/testListener")public String testListener(){applicationContext.publishEvent(new AddDataEvent(this,TestController.class,"test"));return "success";}
    }
    
  • 结果是能够监听到的

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pHjkZeUm-1680567653256)(C:%5CUsers%5Cquyanliang%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5C1680567079331.png)]

  • 如果要使用异步加上@EnableAsync注解,方法上加@Async注解,如下spring boot项目配置

  • @SpringBootApplication
    @EnableAsync
    public class XApplication{public static void main(String[] args) {ConfigurableApplicationContext run = new SpringApplicationBuilder(XApplication.class).web(true).run(args);run.publishEvent("test");}
    }
    
  •     @Async@EventListenerpublic void test(String wrapped){System.out.println("当前线程 "+Thread.currentThread().getName());System.out.println(wrapped);}
    
  • 还可以配置线程池taskExecutor

  • @Configuration
    public class GenericConfiguration {@Beanpublic Executor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//核心线程数:线程池创建时候初始化的线程数//最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程//缓冲队列:用来缓冲执行任务的队列//允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池//线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setQueueCapacity(20);executor.setKeepAliveSeconds(60);executor.setThreadNamePrefix("taskExecutor-");executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());return executor;}
    }
    

源码解析

  • 原理还得从org.springframework.context.event.internalEventListenerProcessor
    说起。

  • 在AnnotationConfigUtils#registerAnnotationConfigProcessors注册了BeanDefinition 对应的是EventListenerMethodProcessor对象 , 而AnnotationConfigUtils是在AnnotationConfigServletWebServerApplicationContext构造方法里被加载。这里要提一下AnnotationConfigServletWebServerApplicationContext,他是spring boot启动入口的重要类(我这里用的是spring boot所以是这个类),可以相当于以前用xml的ClassPathXmlApplicationContext。

  •  public static final String EVENT_LISTENER_PROCESSOR_BEAN_NAME ="org.springframework.context.event.internalEventListenerProcessor";public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {................... // 注册EventListenerMethodProcessor对象if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));}...........................return beanDefs;}
    
  • 注册的EventListenerMethodProcessor对象会在初始化非懒加载对象的时候运行它的afterSingletonsInstantiated方法。
    AbstractApplicationContext#finishBeanFactoryInitialization

  •  protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {............. // 初始化非懒加载对象beanFactory.preInstantiateSingletons();
    }
    
  • DefaultListableBeanFactory#preInstantiateSingletons

  •  @Override
    public void preInstantiateSingletons() throws BeansException {..................// 触发所有适用bean的初始化后回调 主要是afterSingletonsInstantiated方法for (String beanName : beanNames) {
    //如果beanName传入org.springframework.context.event.internalEventListenerProcessor 因为已经上面代码已经初始化,将从缓存中得到一个EventListenerMethodProcessor对象Object singletonInstance = getSingleton(beanName);if (singletonInstance instanceof SmartInitializingSingleton) {final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;if (System.getSecurityManager() != null) {AccessController.doPrivileged((PrivilegedAction<Object>) () -> {smartSingleton.afterSingletonsInstantiated();return null;}, getAccessControlContext());}else {
    // 调用其afterSingletonsInstantiated方法smartSingleton.afterSingletonsInstantiated();}}}
    }
    
  • EventListenerMethodProcessor#afterSingletonsInstantiated

  •  @Override
    public void afterSingletonsInstantiated() {List<EventListenerFactory> factories = getEventListenerFactories();ConfigurableApplicationContext context = getApplicationContext();String[] beanNames = context.getBeanNamesForType(Object.class);for (String beanName : beanNames) {if (!ScopedProxyUtils.isScopedTarget(beanName)) {Class<?> type = null;try {type = AutoProxyUtils.determineTargetClass(context.getBeanFactory(), beanName);}catch (Throwable ex) {// An unresolvable bean type, probably from a lazy bean - let's ignore it.if (logger.isDebugEnabled()) {logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);}}if (type != null) {if (ScopedObject.class.isAssignableFrom(type)) {try {Class<?> targetClass = AutoProxyUtils.determineTargetClass(context.getBeanFactory(), ScopedProxyUtils.getTargetBeanName(beanName));if (targetClass != null) {type = targetClass;}}catch (Throwable ex) {// An invalid scoped proxy arrangement - let's ignore it.if (logger.isDebugEnabled()) {logger.debug("Could not resolve target bean for scoped proxy '" + beanName + "'", ex);}}}try {// 重点是这个方法 处理beanprocessBean(factories, beanName, type);}catch (Throwable ex) {throw new BeanInitializationException("Failed to process @EventListener " +"annotation on bean with name '" + beanName + "'", ex);}}}}
    }
    
  • EventListenerMethodProcessor#processBean;这里有一个重要的类就是ApplicationListenerMethodAdapter,spring把加入了@EventListener注解的方法封装进ApplicationListenerMethodAdapter对象里,然后我们publishEvent方法是,其实是调用的对应的ApplicationListenerMethodAdapter,然后里面是执行这个方法,这里可以看下ApplicationListenerMethodAdapter类的属性。

  • public class ApplicationListenerMethodAdapter implements GenericApplicationListener {protected final Log logger = LogFactory.getLog(getClass());private final String beanName;private final Method method;private final Method targetMethod;private final AnnotatedElementKey methodKey;private final List<ResolvableType> declaredEventTypes;@Nullableprivate final String condition;private final int order;@Nullableprivate ApplicationContext applicationContext;@Nullableprivate EventExpressionEvaluator evaluator;..................................
    }
    
  • protected void processBean(final List<EventListenerFactory> factories, final String beanName, final Class<?> targetType) {if (!this.nonAnnotatedClasses.contains(targetType)) {Map<Method, EventListener> annotatedMethods = null;try {// 拿到使用了@EventListener注解的方法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 = getApplicationContext();for (Method method : annotatedMethods.keySet()) {for (EventListenerFactory factory : factories) {// 判断是否支持该方法  这里用的DefaultEventListenerFactory spring5.0.8 写死的返回trueif (factory.supportsMethod(method)) {//选择方法  beanName 这里是AddDataEventListener的beanName 默认是addDataEventListenerMethod methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));// 这里是创建一个ApplicationListenerMethodAdapter对象ApplicationListener<?> applicationListener =factory.createApplicationListener(beanName, targetType, methodToUse);if (applicationListener instanceof ApplicationListenerMethodAdapter) {// 如果是ApplicationListenerMethodAdapter对象 就把context和evaluator传进去((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);}// 添加到ApplicationListener事件Set集合中去context.addApplicationListener(applicationListener);break;}}}if (logger.isDebugEnabled()) {logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +beanName + "': " + annotatedMethods);}}}
    }
    
  • 后面就是触发事件监听了AbstractApplicationContext#publishEvent

  •  @Override
    public void publishEvent(ApplicationEvent event) {publishEvent(event, null);
    }protected void publishEvent(Object event, @Nullable ResolvableType eventType) {..............................// Multicast right now if possible - or lazily once the multicaster is initializedif (this.earlyApplicationEvents != null) {this.earlyApplicationEvents.add(applicationEvent);}else {// 进入multicastEventgetApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);}// Publish event via parent context as well...if (this.parent != null) {if (this.parent instanceof AbstractApplicationContext) {((AbstractApplicationContext) this.parent).publishEvent(event, eventType);}else {this.parent.publishEvent(event);}}}
    
  • SimpleApplicationEventMulticaster#multicastEvent->invokeListener->doInvokeListener

  • 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().getName())) {// Possibly a lambda-defined listener which we could not resolve the generic event type for// -> let's suppress the exception and just log a debug message.Log logger = LogFactory.getLog(getClass());if (logger.isDebugEnabled()) {logger.debug("Non-matching event type for listener: " + listener, ex);}}else {throw ex;}}
    }
    
  • ApplicationListenerMethodAdapter#onApplicationEvent]

  • @Override
    public void onApplicationEvent(ApplicationEvent event) {processEvent(event);
    }
    ApplicationListenerMethodAdapter#processEventpublic void processEvent(ApplicationEvent event) {Object[] args = resolveArguments(event);if (shouldHandle(event, args)) {// 执行真正的方法Object result = doInvoke(args);if (result != null) {handleResult(result);}else {logger.trace("No result object given - no result to handle");}}
    }
    
  • ApplicationListenerMethodAdapter#doInvoke

  •  protected Object doInvoke(Object... args) {Object bean = getTargetBean();ReflectionUtils.makeAccessible(this.method);try {return this.method.invoke(bean, args);}catch (IllegalArgumentException ex) {assertTargetBean(this.method, bean, args);throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex);}catch (IllegalAccessException ex) {throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex);}catch (InvocationTargetException ex) {// Throw underlying exceptionThrowable targetException = ex.getTargetException();if (targetException instanceof RuntimeException) {throw (RuntimeException) targetException;}else {String msg = getInvocationErrorMessage(bean, "Failed to invoke event listener method", args);throw new UndeclaredThrowableException(targetException, msg);}}
    }
    
  • ApplicationListenerMethodAdapter#getTargetBean

  •  protected Object getTargetBean() {Assert.notNull(this.applicationContext, "ApplicationContext must no be null");return this.applicationContext.getBean(this.beanName);
    }
    

相关文章:

@EventListener注解详细使用(IT枫斗者)

EventListener注解详细使用 简介 EventListener是一种事件驱动编程在spring4.2的时候开始有的&#xff0c;早期可以实现ApplicationListener接口, 为我们提供的一个事件监听、订阅的实现&#xff0c;内部实现原理是观察者设计模式&#xff1b;为的就是业务系统逻辑的解耦,提高…...

[c++17新增语言特性] --- [[nodiscard]]和[[maybe_unused]]

1 [[nodiscard]] 介绍和应用示例 [[nodiscard]] 是C++17引入的一个属性(Attribute),它用于向编译器提示一个函数的返回值应该被检查,避免其被忽略或误用。它可以被用于函数、结构体、类、枚举和 typedef 等声明上,表示如果函数返回值未被使用,或者结构体、类、枚举和 type…...

Centos7安装和使用docker的笔记

最近项目要求用容器部署&#xff0c;所以需要将docker的用法搞清楚&#xff0c;在操作过程中&#xff0c;积累了一些操作方法和技巧&#xff0c;作为笔记&#xff0c;为后面使用做个参考。 首先安装docker需要给centos增加源&#xff08;参考https://www.runoob.com/docker/cen…...

结构像与功能像

导读现代神经成像技术使我们能够研究活体大脑的结构和功能。活体神经成像的益处是显而易见的&#xff0c;而且在基础和临床神经科学中&#xff0c;神经成像已经取得了巨大进展。本文概述了利用活体神经成像研究大脑结构和功能的工作和成就。介绍了几种不同类型的结构MRI成像方法…...

【IAR工程】STM8S基于ST标准库读取DS1302数据

【IAR工程】STM8S基于ST标准库读取DS1302数据✨申明&#xff1a;本文章仅发表在CSDN网站&#xff0c;任何其他网站&#xff0c;未注明来源&#xff0c;见此内容均为盗链和爬取&#xff0c;请多多尊重和支持原创!&#x1f341;对于文中所提供的相关资源链接将作不定期更换。&…...

【SpringBoot】实现后端服务器发送QQ邮件验证码的功能

步骤一、添加邮件相关依赖二、配置邮件服务器三、发送邮件PS&#xff1a;SMTP 发送失败的解决方案一、添加邮件相关依赖 在 pom.xml 文件中添加 JavaMail 和 Spring Mail 相关的依赖。示例代码如下&#xff1a; <dependency><groupId>com.sun.mail</groupId&g…...

vue在input中输入后,按回车,提交数据

vue在input中输入后&#xff0c;按回车&#xff0c;提交数据 1.展示效果如下&#xff1a; 2.代码展示&#xff1a; <div><el-input v-model"toAddNameText" keyup.enter.native"toAddName()" placeholder"回车&#xff0c;即新增该竖杆名称…...

【YOLOX】用YOLOv5框架YOLOX

【YOLOX】用YOLOv5框架YOLOX一、新建common_x.py二、修改yolo.py三、新建yolox.yaml四、训练最近在跑YOLO主流框架的对比实验&#xff0c;发现了一个很奇怪的问题&#xff0c;就是同一个数据集&#xff0c;在不同YOLO框架下训练出的结果差距竟然大的离谱。我使用ultralytics公司…...

【python机器学习实验】——逻辑回归与感知机进行线性分类,附可视化结果!

【python机器学习实验】——逻辑回归与感知机进行线性分类&#xff0c;附可视化结果&#xff01; 下载链接 下载链接 下载链接 可视化效果图&#xff1a; 感知机模型结果为例&#xff1a; 首先&#xff0c;我们有训练数据和测试数据&#xff0c;其每一行为(x,y,label)的形式…...

wps删除的文件怎么恢复

在办公中&#xff0c;几乎每个人都会用到WPS办公软件。它可以帮助我们快速有效地处理各种Word文档、ppt幻灯片、excel表格等。但有文件就会有清理&#xff0c;如果我们不小心删除了wps文件呢?那些wps删除的文件怎么恢复?针对这种情况&#xff0c;小编为大家带来一些恢复WPS文…...

NIO消息黏包和半包处理

1、前言 我们在进行NIO编程时&#xff0c;通常会使用缓冲区进行消息的通信&#xff08;ByteBuffer&#xff09;&#xff0c;而缓冲区的大小是固定的。那么除非你进行自动扩容&#xff08;Netty就是这么处理的&#xff09;&#xff0c;否则的话&#xff0c;当你的消息存进该缓冲…...

day018 第六章 二叉树 part05

一、513.找树左下角的值 这个题目的主要思路是使用广度优先搜索&#xff08;BFS&#xff09;遍历整棵树&#xff0c;最后返回最后一层的最左边的节点的值。具体的实现可以使用队列来存储每一层的节点&#xff0c;并且在遍历每一层节点时&#xff0c;不断更新最左边的节点的值。…...

如何下载ChatGPT-ChatGPT如何写作

CHATGPT能否改一下文章 ChatGPT 作为一种自然语言处理技术&#xff0c;生成的文章可能存在表达不够准确或文风不符合要求等问题。在这种情况下&#xff0c;可以使用编辑和修改来改变输出的文章&#xff0c;使其符合特定的要求和期望。 具体来说&#xff0c;可以采用以下步骤对…...

微策略再次买入

原创&#xff1a;刘教链* * *隔夜&#xff0c;比特币再次小幅回升至28k上方。微策略&#xff08;Microstrategy&#xff09;创始人Michael Saylor发推表示&#xff0c;微策略再次出手&#xff0c;买入1045枚比特币。此次买入大概花费2930万美元&#xff0c;平均加仓成本28016美…...

express框架

Express 是基于 Node.js 平台&#xff0c;快速、开放、极简的 Web 开发框架. 创建一个基本的express web服务器 // 1.导入express const express require(express); // 2.创建web服务器 const app express(); // 3.启动web服务器 app.listen(80, ()>{console.log(expres…...

完蛋的goals

...

Javase学习文档------面象对象初探

引入面向对象 面向对象的由来: 面向对象编程&#xff08;Object-Oriented Programming, OOP&#xff09;是一种编程范型&#xff0c;其由来可以追溯到20世纪60年代。在此之前&#xff0c;主流编程语言采用的是“过程化编程”模式&#xff0c;即面向过程编程模式。在这种模式下&…...

ChatGPT能够干翻谷歌吗?

目前大多数人对于ChatGPT的喜爱&#xff0c;主要源自于其强大的沟通能力&#xff0c;当我们向ChatGPT提出问题时&#xff0c;它不仅能够为我们提供结论&#xff0c;而且还能够与我们建立沟通&#xff0c;向ChatGPT提出任何问题&#xff0c;感觉都像是在与一个真实的人类进行交谈…...

PCL 使用点云创建数字高程模型DEM

目录 一、DEM1、数字高程模型二、代码实现三、结果展示1、点云2、DEM四、相关链接一、DEM 1、数字高程模型 数字高程模型(Digital Elevation Model),简称DEM,是通过有限的地形高程数据实现对地面地形的数字化模拟(即地形表面形态的数字化表达),它是用一组有序数值阵列形…...

我体验了首个接入GPT-4的代码编辑器,太炸裂了

最近一款名为Cursor的代码编辑器已经传遍了圈内&#xff0c;受到众多编程爱好者的追捧。 它主打的亮点就是&#xff0c;通过 GPT-4 来辅助你编程&#xff0c;完成 AI 智能生成代码、修改 Bug、生成测试等操作。 确实很吸引人&#xff0c;而且貌似也能大大节省人为的重复工作&…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1

每日一言 生活的美好&#xff0c;总是藏在那些你咬牙坚持的日子里。 硬件&#xff1a;OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写&#xff0c;"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

HBuilderX安装(uni-app和小程序开发)

下载HBuilderX 访问官方网站&#xff1a;https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本&#xff1a; Windows版&#xff08;推荐下载标准版&#xff09; Windows系统安装步骤 运行安装程序&#xff1a; 双击下载的.exe安装文件 如果出现安全提示&…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

基于matlab策略迭代和值迭代法的动态规划

经典的基于策略迭代和值迭代法的动态规划matlab代码&#xff0c;实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...

Linux nano命令的基本使用

参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时&#xff0c;显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...

[ACTF2020 新生赛]Include 1(php://filter伪协议)

题目 做法 启动靶机&#xff0c;点进去 点进去 查看URL&#xff0c;有 ?fileflag.php说明存在文件包含&#xff0c;原理是php://filter 协议 当它与包含函数结合时&#xff0c;php://filter流会被当作php文件执行。 用php://filter加编码&#xff0c;能让PHP把文件内容…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...