【SpringBoot】万字源码解析——启动流程
Spring Boot启动流程
Spring Boot
的入口类:
@SpringBootApplication
public class IntelGradingApplication {public static void main(String[] args) {SpringApplication.run(IntelGradingApplication.class, args);}
}
Spring Boot
的启动过程可以分为两方面,一个是 new SpringApplication(primarySources)
的初始化过程,一个是 SpringApplication.run()
的应用运行过程。
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class[]{primarySource}, args);
}public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {// 传入primarySources数组,构造SpringApplicationreturn (new SpringApplication(primarySources)).run(args);
}
执行构造函数
构造函数是 Spring Boot 启动过程的第一步,负责初始化各种属性、验证输入并设置必要的上下文,为后续的应用启动和上下文配置奠定基础。Spring Boot 通过一个重载构造函数来接收 null
值的资源加载器和主类数组:
public SpringApplication(Class<?>... primarySources) {this((ResourceLoader)null, primarySources);
}public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {....
}
1.初始化字段
重构的构造函数接收ResourceLoader
和primarySources
参数,并为各种字段进行初始化,比如ResourceLoader
, bannerMode
, headless
等。
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
2.设置主源
将primarySources
转换为LinkedHashSet
,以确保启动过程中按顺序处理,避免重复。
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
3.推断应用类型
通过检查类路径classpath
中的组件(如 "org.springframework.web.reactive.DispatcherHandler
)来确定当前应用的类型:
- REACTIVE:响应式 Web 应用(基于 Reactor 和 WebFlux)。
- SERVLET:传统的 Servlet 类型 Web 应用(如使用 Spring MVC)。
- NONE:非 Web 应用,通常是命令行应用。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
4.加载工厂实例
从 META-INF/spring.factories
中加载配置的初始化器 ApplicationContextInitializer
、BootstrapRegistryInitializer
和监听器 ApplicationListener
。
BootstrapRegistryInitializer
允许在Spring Boot
启动时自定义初始化Bootstrap
注册表。Bootstrap
注册表是一个支持应用上下文的初始化过程的机制,通常用于配置与应用启动相关的共享资源。ApplicationContextInitializer
允许用户在ApplicationContext
刷新前进行进一步的初始化配置操作。这包括但不限于添加属性源、修改环境变量、设置 Bean 定义等。ApplicationListener
,用于监听 Spring Boot 的生命周期事件。这些监听器会在应用启动过程中响应不同的事件,如应用启动事件、环境准备事件、上下文刷新事件等。
// 加载Bootstrap注册表初始化器
this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 加载应用上下文初始化器
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 加载监听器
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
getSpringFactoriesInstances()
方法的核心方法:
loadFactoryNames()
:利用 SpringFactoriesLoader 获取指定工厂的实现类的 Set 集合createSpringFactoriesInstances
:通过反射机制实例化每个工厂的实现类
// 利用Spring工厂加载器获取指定工厂的实现类的set集合
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 通过反射机制实例化每个工厂的实现类
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
5.推断主类
通过 StackTraceElement
找到 main
方法,确定应用程序的入口类。这通常是标记了 @SpringBootApplication
注解的类。
this.mainApplicationClass = this.deduceMainApplicationClass();
执行run方法
1.启动计时器并初始化
-
在执行
run
方法的开头,首先启动一个计时器以记录应用启动的总时长。 -
接着,创建一个
DefaultBootstrapContext
,它会遍历并执行在Bootstrap
注册表中的所有初始化器,以确保启动过程中的必要资源和设置得到正确配置。 -
然后,
ConfigurableApplicationContext
的引用被声明为null
,并配置无头属性,以便在没有用户界面的环境中正常运行。
// 1.1.启动计时器
long startTime = System.nanoTime();
// 1.2.初始化bootstrapContext上下文,该方法会遍历并执行BootStrap注册表中的初始化器
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
// 1.3.声明ApplicationContext为null
ConfigurableApplicationContext context = null;
// 1.4.设置无头属性
this.configureHeadlessProperty();
2.获取并启动监听器
// 2.1.获取监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
// 2.2.通知监听器,应用程序即将启动
listeners.starting(bootstrapContext, this.mainApplicationClass);
在获取监听器的方法 getRunListeners()
中,将所有的监听器封装为一个 SpringApplicationRunListeners 对象,由于在构造函数执行阶段已经加载了监听器对象,在调用方法 getSpringFactoriesInstances
时会直接查询缓存获取对象。
// 2.1.获取监听器
private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class[]{SpringApplication.class, String[].class};// 封装从getSpringFactoriesInstances()方法中获得工厂的所有实现类return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args), this.applicationStartup);
}
通知监听器应用即将启动。这一步骤确保所有监听器能够在应用启动的早期阶段参与并进行必要的初始化。
// 2.2.通知监听器
listeners.starting(bootstrapContext, this.mainApplicationClass)
3.装配环境参数
- 将环境参数与**「引导上下文」**绑定,
prepareEnvironment()
方法会加载应用的外部配置。这包括application.properties
或application.yml
文件中的属性,环境变量,系统属性等。
// 3.1.创建命令行参数对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 3.2.加载应用的外部配置
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 3.3.配置忽略 Bean 信息
this.configureIgnoreBeanInfo(environment);
在prepareEnvironment()
方法中,首先会进行环境配置,还会执行监听器的 environmentPrepared()
方法,表明应用程序的环境已经准备好,最后再将环境绑定到应用程序中。
4.打印横幅
Banner printedBanner = this.printBanner(environment);
5.创建应用上下文
根据构造阶段推断出的 Web 应用类型,创建Spring容器
// 5.调用createApplicationContext方法创建Spring容器
context = this.createApplicationContext();
6.应用上下文准备阶段
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
进入 prepareContext()
方法,具体实现如下:
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {// 6.1.将传入的环境参数应用到上下文中,并调用后处理方法context.setEnvironment(environment);this.postProcessApplicationContext(context);// 6.2.遍历并执行所有注册的初始化器,进一步配置应用上下文this.applyInitializers(context);// 6.3.通知监听器,上下文准备完毕listeners.contextPrepared(context);// 6.4.关闭启动阶段的引导上下文,释放与启动相关的资源bootstrapContext.close(context);// 6.5.打印日志启动信息if (this.logStartupInfo) {this.logStartupInfo(context.getParent() == null);this.logStartupProfileInfo(context);}// 6.6.将命令行参数和横幅注册为Bean,存放到应用上下文中ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);}// 6.7.根据Bean工厂,允许配置循环引用和 bean 定义覆盖if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {((AbstractAutowireCapableBeanFactory)beanFactory).setAllowCircularReferences(this.allowCircularReferences);if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}}// 6.8.设置懒初始化配置if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));// 6.9.加载源,确保应用上下文中定义的 bean 被正确创建。Set<Object> sources = this.getAllSources();Assert.notEmpty(sources, "Sources must not be empty");this.load(context, sources.toArray(new Object[0]));// 6.10.通知监听器,所有的Bean都已经加载但未进行实例化listeners.contextLoaded(context);
}
7.应用上下文刷新阶段
this.refreshContext(context);
- 首先通过加锁确保线程安全,创建并配置
BeanFactory
,这一过程包括注册 Bean 后处理器和事件监听器。 - 在
onRefresh()
方法中,还会启动 Web 服务器。 - 最后,通过配置好的
BeanFactory
实例化所有的Beans
。在这个过程中,BeanFactory
会根据定义的元数据创建和初始化Beans
,并根据需求进行依赖注入,确保整个应用的组件能够顺利协作。
8.应用上下文收尾阶段
// 8.1.afterRefresh()无实际内容,后续版本被移除
this.afterRefresh(context, applicationArguments);
// 8.2.计算应用启动时间
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
}
// 8.3.通知监听器,应用程序启动完成
listeners.started(context, timeTakenToStartup);
9.回调运行器
// 9.1.回调运行器
this.callRunners(context, applicationArguments);
// 9.2.通知监听器
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
该方法从上下文 context
中获取所有已注册的 ApplicationRunner
和 CommandLineRunner
,并结合传入的应用参数 args
执行这些运行器。
这些运行器允许开发者在应用程序启动后执行特定的逻辑,例如初始化数据、设置应用状态或执行启动任务,提供了灵活性和扩展性。
private void callRunners(ApplicationContext context, ApplicationArguments args) {List<Object> runners = new ArrayList();// 9.1.1.从上下文中获取运行器runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());AnnotationAwareOrderComparator.sort(runners);Iterator var4 = (new LinkedHashSet(runners)).iterator();// 9.1.2.结合应用参数执行运行器while(var4.hasNext()) {Object runner = var4.next();if (runner instanceof ApplicationRunner) {this.callRunner((ApplicationRunner)runner, args);}if (runner instanceof CommandLineRunner) {this.callRunner((CommandLineRunner)runner, args);}}
}
10.异常处理
在整个启动过程中,如果出现任何异常,都会被捕获并通过handleRunFailure()
方法进行处理,在该方法中,会通知监听器应用程序启动时出现异常。
该方法会记录错误信息,并通过监听器通知失败事件。最终,抛出IllegalStateException
来中止应用启动,确保调用者能够识别到启动失败的状态。
catch (Throwable var12) {ex = var12;this.handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);
}
SpringApplicationRunListeners监听器
SpringApplicationRunListeners
是一个具体的类。它实现了 Spring Boot 中的监听器机制,用于在应用程序的不同启动阶段通知注册的监听器(SpringApplicationRunListener
接口的实现类)。通过这个类,Spring Boot 可以在应用启动过程中管理多个监听器,处理各种生命周期事件。
SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners, ApplicationStartup applicationStartup) {// 记录日志this.log = log;// 保存已注册的监听器列表,这些监听器会对应用程序生命周期事件做出响应this.listeners = new ArrayList(listeners);// 跟踪启动步骤,以便进行性能监控this.applicationStartup = applicationStartup;
}
SpringApplicationRunListener
是 Spring Boot 中的一个接口,用于在应用启动过程的不同阶段提供回调。实现这个接口允许监听并响应应用生命周期中的关键事件。该接口定义了多个方法,每个方法对应启动过程中的特定阶段,包括:
- starting(): 在运行开始时调用,此时尚未开始任何处理,可以用于初始化在启动过程中需要的资源。
- environmentPrepared(): 当
SpringApplication
准备好Environment
但在创建ApplicationContext
之前调用,这是修改应用环境属性的好时机。 - contextPrepared(): 当
ApplicationContext
准备好但在加载之前调用,可以用于对上下文进行预处理。 - contextLoaded(): 当
ApplicationContext
被加载但在刷新之前调用,此时所有的 Bean 定义都已加载,但尚未实例化。 - started(): 在
ApplicationContext
刷新之后、任何应用运行器和命令行运行器被调用之前调用,此时应用已经准备好接收请求。 - running(): 在运行器被调用之后、应用启动完成之前调用,这是在应用启动并准备好服务请求时执行某些动作的好时机。
- failed(): 如果启动过程中出现异常,则调用此方法。
SpringFactoriesLoader原理
SpringFactoriesLoader
方法会根据传入的工厂类和类加载器,从 META-INF/spring.factories
文件中加载**「指定类型对应的工厂类名称」**。
loadFactoryNames()
loadFactoryNames
方法是一个高级 API,它通过获取入参中的全限定类名 factoryTypeName
,在内部调用 loadSpringFactories()
方法获取返回的 Map 集合,并根据 factoryTypeName
获取了 Map 中的实现类的 List 集合。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {ClassLoader classLoaderToUse = classLoader;if (classLoaderToUse == null) {classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}// 获取工厂类的全限定名String factoryTypeName = factoryType.getName();// 从 loadSpringFactories 返回的 Map 中获取指定类型工厂的实现类return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
loadSpringFactories()
loadSpringFactories
方法是更加底层的方法,通过缓存机制和类加载器获取 spring.factories
文件中所有配置的工厂及其实现类,将这些信息封装为 Map
集合后返回给上游的 API。
缓存机制
方法会检查是否已经通过当前类加载器加载过 spring.factories
文件。如果缓存 (cache
) 中已经存在相应的工厂信息,直接返回缓存的 Map<String, List<String>>
,避免重复加载。
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {return result;
}
加载 META-INF/spring.factories
方法会通过类加载器查找所有路径下名为 META-INF/spring.factories
的文件。由于每个 JAR 包都可能包含一个 META-INF/spring.factories
文件,方法会返回一个 Enumeration<URL>
对象,表示找到的所有相关资源文件。
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
解析spring.factories文件
通过迭代逐个读取每个找到的 spring.factories
文件。对于每个文件,使用 PropertiesLoaderUtils.loadProperties()
将文件内容解析为 Properties
对象。
每个 Properties
对象对应一个 spring.factories
文件的内容,其中 key 是工厂类型(例如 org.springframework.context.ApplicationContextInitializer
),value 是逗号分隔的工厂实现类列表。
while(urls.hasMoreElements()) {URL url = (URL)urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);
将工厂类型和实现类存入Map
遍历 Properties
的 entrySet()
。对于每个 entry
,key
是工厂类型的全限定类名,value
是对应的工厂实现类名(逗号分隔)。
工厂类型名称通过 entry.getKey()
获取,并使用 String.trim()
去除可能的空白字符。工厂实现类则将逗号分隔的字符串转换为实现类的数组。
while(var6.hasNext()) {Map.Entry<?, ?> entry = (Map.Entry)var6.next();String factoryTypeName = ((String)entry.getKey()).trim();String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());String[] var10 = factoryImplementationNames;int var11 = factoryImplementationNames.length;for(int var12 = 0; var12 < var11; ++var12) {String factoryImplementationName = var10[var12];((List)result.computeIfAbsent(factoryTypeName, (key) -> {return new ArrayList();})).add(factoryImplementationName.trim());}
}
相关文章:
【SpringBoot】万字源码解析——启动流程
Spring Boot启动流程 Spring Boot 的入口类: SpringBootApplication public class IntelGradingApplication {public static void main(String[] args) {SpringApplication.run(IntelGradingApplication.class, args);} }Spring Boot 的启动过程可以分为两方面&am…...

Nginx 配置初步 下
Nginx 配置初步(下) 一行代表一个指令; 每个指令有其上下文环境,比如 listen 指令只能在 http 指令块中出现,不能单独出现。1. Http 服务配置初步 1.1 常用指令 Nginx 的所有模块,打开模块我们就能看到模块中支持的指令。最常用…...

可视化ETL平台-Kettle的安装及简单使用
本章知识简介 主线A: 自连接查询; 主线B: 安装JDK与Kettle; 主线C: 使用Kettle工具. 本章目标: 1: 知道使用一张表可以实现自连接查询; [了解]注意: 左表、右表都是同一张表 2: 了解Kettle环境的安装流程; [了解]a.安装JDKb.安装Kettle 3: 熟悉使用kettle将txt数…...
java8 动态加载jar包至系统的classpath
1. io.test包 创建MyMain.java类,创建addJarToClasspath方法将jar包动态加载进系统的classpath中 package io.test;import java.io.File; import java.net.URL; import java.net.URLClassLoader; import java.lang.reflect.Method;public class MyMain {public st…...
C++二级题 计算好数:1数大于0数(二进制的位运算)
1、题目 若将一个正整数化为二进制数,在此二进制数中,我们将数字1的个数多于数字0的个数的这类二进制数称为好数。 例如: (13)10 (1101)2,其中1的个数为3,0的个数为1,则此数是好数; (10)10 (1…...

数字孪生城市:智慧城市的未来蓝图
在当今数字化时代,智能技术的广泛应用正在改变人们的生活和工作方式。数字孪生城市作为未来新型智慧城市演进的重要方向,数字孪生城市是一种将城市物理世界的各个方面转化为数字形式的技术,通过网络空间与物理世界之间的实时数据交换和仿真分…...

Java篇图书管理系统
目录 前言 一. 图书管理系统的核心 二. 图书管理系统基本框架 2.1 book包 2.1.1 Book(书籍类) 2.1.2 Booklist (书架类) 2.2 user包 2.2.1 User类 2.2.2 Administrator(管理员类) 2.2.3 Visitor(用户类) 2.…...

BUUCTF之web篇
第一题 [极客大挑战 2019]EasySQL 打开靶机后可以看到这是一个登陆的页面 我们可以尝试两种方式登录 弱口令爆破(burpsuite) 通过SQL注入里的万能密码来跳过账户和密码验证的过程 这里就需要万能密码aor true # 在这里单引号的作用是结束用户名或者密码…...

010——二叉树(2)线索化
引入: 问题1: n个节点的二叉树,用二叉链表存储,问在这个二叉链表中一共有 __个指针域? 其中,有 __个指针域不为NULL,__个指针域为NULL? 答:2n n-1 n1 在二叉链表中…...
鸿蒙拍照小助手02
项目文件目录 为了确保项目文件目录清晰,以下是完整的项目文件目录结构: code 拍照小助手/ │ ├── entry/ │ ├── src/ │ │ ├── main/ │ │ │ ├── js/ │ │ │ │ └── 默认/ │ │ │ │ ├── 页面/ │ │ │ │ │ ├── 主页/ │ │ │ │ │ │ ├…...
lua while循环
软考鸭微信小程序 过软考,来软考鸭! 提供软考免费软考讲解视频、题库、软考试题、软考模考、软考查分、软考咨询等服务 Lua作为一种小巧精致的语言,特别适用于嵌入其他程序提供脚本支持。在编程中,循环结构是不可或缺的一部分,而while循环则是…...

JAVA篇之类和对象
目录 一. 面向对象 1.1 面向对象和面向过程 二. 类的定义和使用 2.1 什么是类 2.2 类的定义格式 三. 类的实例化 四. this引用 4.1 this引用的作用 五. 构造方法 5.1 构造方法重载 5.2 通过this调用其他构造方法 5.3 默认初始化 结语 一. 面向对象 Java 是一门面向对…...
IO流详解_CoderLix
主要内容 File类IO流字节流字符流异常处理Properties缓冲流转换流序列化流打印流 File类 1.1 概述 java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。 1.2 构造方法 public File(String pathname) :通过…...

241023-RHEL非管理员安装Docker并开放指定宿主机端口部署Gitlab
A. RHEL非管理员安装Docker 要在没有管理员权限的情况下离线安装 Docker 和 Docker Compose,虽然受到一定限制,仍有一些可行的步骤可以帮助你在有限权限下完成这项任务。需要注意的是,这种方式适用于本地用户环境下的 Docker 安装࿰…...
python ubuntu安装加速
ubuntu升级python到python3.11(可能是全网最靠谱的方法,亲测有效)_ubuntu python3.11-CSDN博客 python-release安装包下载_开源镜像站-阿里云...
100种算法【Python版】第12篇——快速幂算法
本文目录 1 基本原理2 基本步骤3 数学示例4 python代码1 基本原理 快速幂算法(Fast Exponentiation)是一种高效计算整数幂的方法,尤其适用于计算大数的幂。其主要思想是利用分治法和二进制表示来减少乘法运算的次数,从而加快计算速度。 计算 x n x^n x...

Java多线程详解②(全程干货!!!)Thread Runnable
这里是Themberfue 上节主要讲完了多线程的一些基础知识,这节通过代码进一步理解多线程🫡 多线程 Java标准库中提供了Thread类,以程序员们编写多线程代码,我们可以查看官方文档进一步了解Thread的特性以及提供的接口。 类似于Sy…...
机器学习——图神经网络
图神经网络(GNN):理解复杂网络数据的有效工具 图神经网络(Graph Neural Network, GNN)是近年来机器学习领域的热门话题。GNN 以图结构数据为核心,能够高效地捕捉节点和边的复杂关系,广泛应用于社交网络、推荐系统、生…...

一、在cubemx下RTC配置调试实例测试
一、rtc的时钟有lse提供。 二、选择rtc唤醒与闹钟功能 内部参数介绍 闹钟配置 在配置时间时,注意将时间信息存储起来,防止复位后时间重新配置。 if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0)! 0x55AA)//判断标志位是否配置过,没有则进…...
【Nas】X-DOC:Mac mini Docker部署中国特供版Jellyfin
【Nas】X-DOC:Mac mini Docker部署中国特供版Jellyfin 1、拉取镜像:2、启动镜像3、访问服务4、参考文档 Mac mini Docker部署中国特供版Jellyfin 1、拉取镜像: docker pull nyanmisaka/jellyfin:230901-amd64jellyfin 10.8.10版本ÿ…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...