SpringBoot JAR 启动原理

文章目录

    • 版本
    • 概述
    • JAR 包结构
    • `MANIFEST.MF` 描述文件
    • JarLauncher
      • `Archive` 接口
      • `launch` 方法
      • `Handlers.register()` 方法
      • `getClassPathUrls` 方法
      • `createClassLoader` 方法
    • 时序图
    • 参考

版本

  • Java 17
  • SpringBoot 3.2.4

概述

JAR 启动原理可以简单理解为“java -jar的启动原理”

SpringBoot 提供了 Maven 插件 spring-boot-maven-plugin,可以将 SpringBoot 项目打包成 JAR 包,这个跟普通 JAR 包有所不同

  • 普通 JAR 包:可以被其他项目引用,解压后就是包名,包里就是代码
  • SpringBoot 打包的 JAR 包:只能运行,不能被其他项目依赖,包里 \BOOT-INF\classes 目录才是代码

使用 maven package 指令执行打包命令,将项目打包成 JAR 包,根据 pom.xml 文件中的 nameversion 标签作为 JAR 包名称,比如项目的
pom.xml 配置文件有 <name>springboot-demo</name><version>0.0.1-SNAPSHOT</version>,执行 maven package 命令之后打包出来之后为 springboot-demo-0.0.1-SNAPSHOT.jarspringboot-demo-0.0.1-SNAPSHOT.jar.original

  • springboot-demo-0.0.1-SNAPSHOT.jar 之类的 JAR 包:spring-boot-maven-plugin 生成的 JAR 包。包含了应用的第三方依赖,SpringBoot 相关的类,存在嵌套的 JAR 包,称之为 executable jar 或 fat jar。也就是最终可运行的 SpringBoot 的 JAR 包。可以直接执行 java -jar 指令启动
  • springboot-demo-0.0.1-SNAPSHOT.jar.original 之类的 JAR 包:默认 maven-jar-plugin 生成的 JAR 包,仅包含编译用的本地文件。也就是打包之前生成的原始 JAR 包,仅包含你项目本身的 class 文件和资源文件,不包含依赖项,也不具备 Spring Boot 的启动结构。通常由 Spring Boot Maven 插件在打包过程中中间步骤生成,Spring Boot 会在这个基础上重打包(repackage)为可运行的 JAR 文件。

JAR 包结构

springboot-demo-0.0.1-SNAPSHOT.jar 之类的 JAR 包中通常包括 BOOT-INFMETA-INForg 三个文件夹

  • META-INF:通过 MANIFEST.MF 文件提供 jar 包的元数据,声明了 JAR 的启动类
  • org:为 SpringBoot 提供的 spring-boot-loader 项目,它是 java -jar 启动 Spring Boot 项目的秘密所在
  • BOOT-INF/lib:SpringBoot 项目中引入的依赖的 JAR 包,目的是解决 JAR 包里嵌套 JAR 的情况,如何加载到其中的类
  • BOOT-INF/classes:Java 类所编译的 .class、配置文件等等

应用程序类应该放在嵌套的BOOT-INF/classes目录中。依赖项应该放在嵌套的BOOT-INF/lib目录中。

├── BOOT-INF // 文件目录存放业务相关的,包括业务开发的类和配置文件,以及依赖的 JAR
│   ├── classes
│   │   ├── application.yaml
│   │   └── com
│   │       └── example
│   │           └── springbootdemo
│   │               ├── OrderProperties.class
│   │               ├── SpringbootDemoApplication.class // 启动类
│   │               ├── SpringbootDemoApplication$OrderPropertiesCommandLineRunner.class
│   │               ├── SpringbootDemoApplication$ValueCommandLineRunner.class
│   │               ├── SpringMVCConfiguration.class
│   │               └── vo
│   │                   └── UserVO.class
│   ├── classpath.idx
│   ├── layers.idx
│   └── lib
│       ├── spring-aop-6.1.6.jar
│       ├── spring-beans-6.1.6.jar
│       ├── spring-boot-3.2.5.jar
│       ├── spring-boot-autoconfigure-3.2.5.jar
│       ├── spring-boot-jarmode-layertools-3.2.5.jar
├── META-INF // MANIFEST.MF 描述文件和 maven 的构建信息
│   ├── MANIFEST.MF
│   ├── maven
│   │   └── com.example
│   │       └── springboot-demo
│   │           ├── pom.properties // 配置文件
│   │           └── pom.xml
│   ├── services
│   │   └── java.nio.file.spi.FileSystemProvider
│   └── spring-configuration-metadata.json
└── org└── springframework└── boot└── loader // SpringBoot loader 相关类├── jar│   ├── ManifestInfo.class│   ├── MetaInfVersionsInfo.class├── jarmode│   └── JarMode.class├── launch│   ├── Archive.class│   ├── Archive$Entry.class├── log│   ├── DebugLogger.class│   ├── DebugLogger$DisabledDebugLogger.class│   └── DebugLogger$SystemErrDebugLogger.class├── net│   ├── protocol│   │   ├── Handlers.class│   │   ├── jar│   │   │   ├── Canonicalizer.class│   │   │   ├── Handler.class│   │   └── nested│   │       ├── Handler.class│   │       ├── NestedLocation.class│   └── util│       └── UrlDecoder.class├── nio│   └── file│       ├── NestedByteChannel.class│       ├── NestedByteChannel$Resources.class├── ref│   ├── Cleaner.class│   └── DefaultCleaner.class└── zip├── ByteArrayDataBlock.class├── CloseableDataBlock.class

MANIFEST.MF 描述文件

MANIFEST.MF 是 Java JAR(Java Archive)文件中的一个核心元数据文件,用于描述 JAR 包的配置信息和依赖关系。它位于 JAR 文件内部的 META-INF/ 目录下,是 JVM 启动可执行 JAR 或加载依赖的关键依据。

java -jar 命令引导的具体启动类必须配置在 MANIFEST.MF 描述文件中的 Main-Class 属性中,该命令用来引导标准执行的 JAR 文件,读取的就是 MANIFEST.MF 文件中的 Main-Class 属性值,Main-Class 属性就是定义包含了 main 方法的类代表了应用程序执行入口类

Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.3.0
Build-Jdk-Spec: 19
Implementation-Title: springboot-demo
Implementation-Version: 0.0.1-SNAPSHOT
Main-Class: org.springframework.boot.loader.launch.JarLauncher
Start-Class: com.example.springbootdemo.SpringbootDemoApplication
Spring-Boot-Version: 3.2.5
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
  • Main-Class:定义程序入口类,格式为完全限定类名(含包名),JVM 通过此属性找到 public static void main(String[] args) 方法启动程序。设置为 spring-boot-loader 项目的 JarLauncher 类,进行 SpringBoot 应用的启动。
  • Start-Class:SpringBoot 规定的主启动类
  • Class-Path:指定依赖的 JAR 文件或目录,路径用空格分隔
    • 路径相对于 JAR 文件的位置(非当前工作目录)
    • 依赖需放在 JAR 同级目录的指定路径下
  • Manifest-Version:指定清单文件版本
  • Created-By:生成 JAR 的工具信息(如 JDK 版本或构建工具)
  • Implementation-Version:JAR 的版本号(用于版本管理)

虽然 Start-Class 已经指向了主启动类路径,但是不能直接启动

  • 原因一:因为在 JAR 包中,主启动类并不在这个路径上,而是在在 BOOT-INF/classes 目录下,不符合 Java 默认的 JAR 包的加载规则。因此,需要通过 JarLauncher 启动加载。
  • 原因二:Java 规定可执行器的 JAR 包禁止嵌套其它 JAR 包。但是可以看到 BOOT-INF/lib 目录下,实际有 SpringBoot 应用依赖的所有 JAR 包。因此,spring-boot-loader 项目自定义实现了 ClassLoader 实现类 LaunchedURLClassLoader,支持加载 BOOT-INF/classes 目录下的 .class 文件,以及 BOOT-INF/lib 目录下的 jar 包。

JarLauncher

JarLauncherSpring Boot 框架中用于启动可执行 JAR 文件的核心类,属于 org.springframework.boot.loader 包。它的核心作用是为 Spring Boot 的“胖 JAR”(Fat JAR)提供自定义的启动机制,解决传统 JAR 无法直接加载嵌套依赖的问题。

位于 JAR 包中的 org.springframework.boot.loader.launch.JarLauncher

继承类:Launcher -> ExecutableArchiveLauncher -> JarLauncher

public class JarLauncher extends ExecutableArchiveLauncher {public JarLauncher() throws Exception {}protected JarLauncher(Archive archive) throws Exception {super(archive);}protected boolean isIncludedOnClassPath(Archive.Entry entry) {return isLibraryFileOrClassesDirectory(entry);}protected String getEntryPathPrefix() {return "BOOT-INF/";}static boolean isLibraryFileOrClassesDirectory(Archive.Entry entry) {String name = entry.name();return entry.isDirectory() ? name.equals("BOOT-INF/classes/") : name.startsWith("BOOT-INF/lib/");}public static void main(String[] args) throws Exception {(new JarLauncher()).launch(args);}
}

通过 (new JarLauncher()).launch(args) 创建 JarLauncher 对象,调用 launch 方法进行启动,整体逻辑还是通过父类的父类 Laucher 所提供。

Archive 接口

根据类图可知,JarLauncher 继承于 ExecutableArchiveLauncher 类,在 ExecutableArchiveLauncher 类源码中有对 Archive 对象的构造

public abstract class ExecutableArchiveLauncher extends Launcher {public ExecutableArchiveLauncher() throws Exception {this(Archive.create(Launcher.class));}
}

Archive 接口,是 spring-boot-loader 项目抽象出来的用来统一访问资源的接口,ExplodedArchive 是针对目录的 Archive 实现类,JarFileArchive 是针对 JAR 的 Archive 实现类,所以根据 isDirectory 方法进行判断。

  • Archive 概念即归档文档概念,在 Linux 下比较常见
  • 通常就是一个 tar/zip 格式的压缩包
  • JAR 是 zip 格式
public interface Archive extends AutoCloseable {static Archive create(Class<?> target) throws Exception {return create(target.getProtectionDomain());}static Archive create(ProtectionDomain protectionDomain) throws Exception {CodeSource codeSource = protectionDomain.getCodeSource();URI location = codeSource != null ? codeSource.getLocation().toURI() : null;// 拿到当前 classpath 的绝对路径String path = location != null ? location.getSchemeSpecificPart() : null;if (path == null) {throw new IllegalStateException("Unable to determine code source archive");} else {return create(new File(path));}}static Archive create(File target) throws Exception {if (!target.exists()) {throw new IllegalStateException("Unable to determine code source archive from " + target);} else {return (Archive)(target.isDirectory() ? new ExplodedArchive(target) : new JarFileArchive(target));}}
}

launch 方法

在其父类 Laucher 中可以看出,launcher 方法可以读取 JAR 包中的类加载器,保证 BOOT-INF/lib 目录下的类和 BOOT-classes 内嵌的 jar 中的类能够被正常加载到,之后执行 Spring Boot 应用的启动。

public abstract class Launcher {protected void launch(String[] args) throws Exception {// 如果当前不是解压模式(!this.isExploded()),则注册处理器(Handlers.register())if (!this.isExploded()) {Handlers.register();}try {// 创建类加载器(ClassLoader)用于加载类路径上的类ClassLoader classLoader = this.createClassLoader((Collection)this.getClassPathUrls());// 根据系统属性 "jarmode" 判断是否使用特定的 JAR 模式运行器类名String jarMode = System.getProperty("jarmode");String mainClassName = this.hasLength(jarMode) ? JAR_MODE_RUNNER_CLASS_NAME : this.getMainClass();// 使用创建的类加载器和主类名调用 launch 方法启动应用this.launch(classLoader, mainClassName, args);} catch (UncheckedIOException var5) {UncheckedIOException ex = var5;throw ex.getCause();}}protected void launch(ClassLoader classLoader, String mainClassName, String[] args) throws Exception {Thread.currentThread().setContextClassLoader(classLoader);Class<?> mainClass = Class.forName(mainClassName, false, classLoader);Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);mainMethod.setAccessible(true);mainMethod.invoke((Object)null, args);}
}

Handlers.register() 方法

逐步分析 launcher 方法,首先方法中调用的 Handlers.register() 方法,用于动态注册自定义协议处理器包,并确保 URL 流处理器缓存被正确刷新。

public final class Handlers {private static final String PROTOCOL_HANDLER_PACKAGES = "java.protocol.handler.pkgs";private static final String PACKAGE = Handlers.class.getPackageName();private Handlers() {}public static void register() {// 获取系统属性java.protocol.handler.pkgs,该属性用于指定协议处理器的包名String packages = System.getProperty("java.protocol.handler.pkgs", "");// 如果当前包名未包含在属性中,则将其追加到属性值中(以|分隔)packages = !packages.isEmpty() && !packages.contains(PACKAGE) ? packages + "|" + PACKAGE : PACKAGE;System.setProperty("java.protocol.handler.pkgs", packages);// 清除URL流处理器缓存resetCachedUrlHandlers();}private static void resetCachedUrlHandlers() {try {// 强制JVM重新加载URL流处理器URL.setURLStreamHandlerFactory((URLStreamHandlerFactory)null);} catch (Error var1) {}}
}

getClassPathUrls 方法

分析 ClassLoader classLoader = this.createClassLoader((Collection)this.getClassPathUrls()); 中的 (Collection)this.getClassPathUrls() 方法,调用 getClassPathUrls 方法返回值作为参数,该方法为抽象方法,具体实现在 ExecutableArchiveLauncher

public abstract class ExecutableArchiveLauncher extends Launcher {protected Set<URL> getClassPathUrls() throws Exception {return this.archive.getClassPathUrls(this::isIncludedOnClassPathAndNotIndexed, this::isSearchedDirectory);}
}

ExecutableArchiveLaunchergetClassPathUrls 方法执行 Archive 接口定义的 getClassPathUrls 方法返回的是包含所有匹配 URL 的有序集合

class JarFileArchive implements Archive {// 通过流处理遍历JAR条目,应用过滤器筛选后转换为URLpublic Set<URL> getClassPathUrls(Predicate<Archive.Entry> includeFilter, Predicate<Archive.Entry> directorySearchFilter) throws IOException {return (Set)this.jarFile.stream().map(JarArchiveEntry::new).filter(includeFilter).map(this::getNestedJarUrl).collect(Collectors.toCollection(LinkedHashSet::new));}// 根据条目注释判断是否为解压存储的嵌套JAR,若是则调用特殊处理方法,否则直接创建标准URL// archiveEntry:BOOT-INF/classes/private URL getNestedJarUrl(JarArchiveEntry archiveEntry) {try {JarEntry jarEntry = archiveEntry.jarEntry();String comment = jarEntry.getComment();return comment != null && comment.startsWith("UNPACK:") ? this.getUnpackedNestedJarUrl(jarEntry) : JarUrl.create(this.file, jarEntry);} catch (IOException var4) {IOException ex = var4;throw new UncheckedIOException(ex);}}
}

createClassLoader 方法

分析 ClassLoader classLoader = this.createClassLoader((Collection)this.getClassPathUrls()); 中的 createClassLoader 方法

LaunchedClassLoader 是 SpringBoot 自定义的类加载器,位于 org.springframework.boot.loader.LaunchedURLClassLoader, 专门用于加载 Spring Boot 可执行 JAR(即“胖 JAR”)中嵌套的依赖和资源。它的核心作用是解决传统 Java 类加载器无法直接加载 JAR 内嵌 JAR(如 BOOT-INF/lib/ 中的依赖)的问题。且LaunchedClassLoader 在加载类时,会先尝试自己加载(从嵌套 JAR 或用户代码),若找不到再委派父类加载器。这是对传统双亲委派机制的扩展,确保优先加载应用自身的类。

public abstract class Launcher {protected ClassLoader createClassLoader(Collection<URL> urls) throws Exception {return this.createClassLoader((URL[])urls.toArray(new URL[0]));}private ClassLoader createClassLoader(URL[] urls) {ClassLoader parent = this.getClass().getClassLoader();return new LaunchedClassLoader(this.isExploded(), this.getArchive(), urls, parent);}
}

时序图

+-----------------+     +--------------+     +----------------------+     +-------------------+
|      JVM        |     | JarLauncher  |     | LaunchedURLClassLoader|     | MainMethodRunner  |
+-----------------+     +--------------+     +----------------------+     +-------------------+|                     |                         |                           || 执行 java -jar app.jar |                       |                           ||--------------------->|                         |                           ||                     | 创建 Archive 对象        |                           ||                     |------------------------>|                           ||                     | 解析 MANIFEST.MF         |                           ||                     |<------------------------|                           ||                     | 调用 launch()            |                           ||                     |------------------------>|                           ||                     |                         | 创建类加载器              ||                     |                         |<--------------------------||                     |                         | 加载 BOOT-INF/classes/lib||                     |                         |-------------------------->||                     |                         |                           | 反射加载 Start-Class|                     |                         |                           |<------------------||                     |                         |                           | 调用 main()|                     |                         |                           |------------------>||                     |                         |                           | 执行用户代码       ||<------------------------------------------------------------(结果或异常)|| JVM 退出            |                         |                           ||<--------------------|                         |                           |

参考

  • 一文搞懂 Spring Boot 中 java -jar 的启动 jar 包的原理_springboot java -jar-CSDN 博客
  • 芋道 Spring Boot Jar 启动原理 | 芋道源码 —— 纯源码解析博客
  • Site Unreachable

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

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

相关文章

YOLO11解决方案之速度估算探索

概述 Ultralytics提供了一系列的解决方案&#xff0c;利用YOLO11解决现实世界的问题&#xff0c;包括物体计数、模糊处理、热力图、安防系统、速度估计、物体追踪等多个方面的应用。 YOLO速度估算结合物体检测和跟踪技术&#xff0c;使用YOLO11 模型检测每帧中的物体&#xf…

初识C++:模版

本篇博客主要讲解C模版的相关内容。 目录 1.泛型编程 2.函数模板 2.1 函数模版概念 2.2 函数模版格式 2.3 函数模版的原理 2.4 函数模版的实例化 1.隐式实例化&#xff1a;让编译器根据实参推演模板参数的实际类型 2. 显式实例化&#xff1a;在函数名后的<>中指定模…

人工智能100问☞第27问:神经网络与贝叶斯网络的关系?

神经网络与贝叶斯网络是两种互补的智能模型:神经网络通过多层非线性变换从数据中学习复杂模式,擅长大规模特征提取和预测,而贝叶斯网络基于概率推理建模变量间的条件依赖关系,擅长处理不确定性和因果推断。两者的融合(如贝叶斯神经网络)结合了深度学习的表征能力与概率建…

【node.js】入门基础

个人主页&#xff1a;Guiat 归属专栏&#xff1a;node.js 文章目录 1. Node.js简介1.1 Node.js的核心特点1.2 Node.js适用场景 2. 第一个Node.js程序2.1 创建并运行Hello World2.2 创建简单的HTTP服务器 3. Node.js核心概念3.1 模块系统3.1.1 创建和导出模块3.1.2 导入和使用模…

百度飞桨PaddleOCR 3.0开源发布 OCR精度跃升13%

百度飞桨 PaddleOCR 3.0 开源发布 2025 年 5 月 20 日&#xff0c;百度飞桨团队正式发布了 PaddleOCR 3.0 版本&#xff0c;并将其开源。这一新版本在文字识别精度、多语种支持、手写体识别以及高精度文档解析等方面取得了显著进展&#xff0c;进一步提升了 PaddleOCR 在 OCR …

Android 14 Binderized HAL开发实战指南(AIDL版)

Android 14 Binderized HAL开发实战指南&#xff08;AIDL版&#xff09; 环境要求 Android 14源码编译环境AOSP android-14.0.0_r7分支Soong build系统Java 17 & NDK r25c 项目结构 hardware/interfaces/myservice/ ├── 1.0 │ ├── IMyHalService.aidl # AID…

第九天的尝试

目录 一、每日一言 二、练习题 三、效果展示 四、下次题目 五、总结 一、每日一言 创造美好的代价是努力&#xff0c;失望以及毅力&#xff0c;首先是痛苦&#xff0c;然后才是欢乐。 时间是快的&#xff0c;看怎么利用&#xff0c;安排好一切事情&#xff0c;才能从容面对…

交安安全员:交通工程安全领域的关键角色

在交通工程这个庞大而复杂的领域中&#xff0c;交安安全员扮演着举足轻重的角色&#xff0c;他们是安全的捍卫者&#xff0c;是交通工程顺利推进的重要保障。​ 交安安全员&#xff0c;专门从事公路水运工程施工企业安全生产管理工作。他们的专业身份由交通运输部门颁发的交安…

实验-设计一个应用系统(计算机组成原理)

目录 一. 实验内容 二. 实验步骤 &#xff08;1&#xff09;七段数码管显示模块 &#xff08;2&#xff09;指令模块 &#xff08;3&#xff09;控制模块 &#xff08;4&#xff09;ALU模块 &#xff08;5&#xff09;CPU模块 三. 实现效果 四. 实验环境 五. 实验小结…

【博客系统】博客系统第四弹:令牌技术

令牌机制 为什么不能使用 Session 实现登录功能&#xff1f; 传统思路&#xff1a; 登录页面把用户名密码提交给服务器。服务器端验证用户名密码是否正确&#xff0c;并返回校验结果给前端。如果密码正确&#xff0c;则在服务器端创建 Session。通过 Cookie 把 sessionId 返回…

【瑞数3代】药监评审中心逆向分析 | 后缀MmEwMD参数

1.目标 目标网址&#xff1a;https://www.cde.org.cn/main/news/listpage/545cf855a50574699b46b26bcb165f32 import requestscookies {FSSBBIl1UgzbN7N80S: 8sYeMWaC_IHoNl8Ckfx2y9MLiueMCkPr2V3MIoZkrMPUfzMMaXKzAoxpNPvyw4lt,Path: /,FSSBBIl1UgzbN7N80T: 3js3ygV.St6BvO20…

【漫话机器学习系列】274.基尼指数(Gini Index)

决策树中的基尼指数&#xff08;Gini Index&#xff09;详解 —— 从公式理解到实际应用 在构建决策树模型时&#xff0c;一个核心问题是&#xff1a;如何选择最优的特征来进行节点划分&#xff1f; 这就涉及到了“划分准则”的问题。常见的准则有信息增益、信息增益率以及本文…

R语言学习--Day07--T分布与T检验

昨天我们介绍了R中用于对数据进行分类的聚类分析的方法&#xff0c;接下来我们来看T分布。 T分布 T分布适用于帮我们估计整组数据&#xff08;较小的数据量&#xff0c;一般小于30&#xff09;的真实值在哪一个区间&#xff0c;具体是计算置信区间&#xff08;一般为95%&#…

数据结构与算法-线性表-双向链表(Double Linked List)

1 线性表 1.4 双向链表&#xff08;Double Linked List&#xff09; 双向链表的结点中有两个指针域&#xff0c;一个指向直接后继&#xff0c;另一个指向直接前驱&#xff0c;主要是为了解决前向查找的问题。 双向链表结构&#xff1a; 书籍和视频教程都只讲解了插入和删除的…

甘特图实例 dhtmlxGantt.js

本文介绍了如何使用dhtmlxGantt库创建一个基础的甘特图示例&#xff0c;并对其进行汉化和自定义配置。首先&#xff0c;通过引入dhtmlxgantt.css和dhtmlxgantt.js文件初始化甘特图。接着&#xff0c;通过设置gantt.i18n.setLocale("cn")实现核心文本的汉化&#xff0…

C++23 新增扁平化关联容器详解

文章目录 一、引言已有关联容器回顾新容器的引入原因 二、std::flat_set定义与特性代码示例适用场景 三、std::flat_multiset定义与特性代码示例适用场景 四、std::flat_map定义与特性代码示例适用场景 五、std::flat_multimap定义与特性代码示例适用场景 六、与其他容器的比较…

使用zap,对web应用/API接口 做安全检测

https://www.zaproxy.org/getting-started/ 检测方法 docker pull ghcr.io/zaproxy/zaproxy:stable# 执行baseline测试 docker run -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \ -t https://baseline.yeshen.org# 执行api测试 docker run -t ghcr.io/zaproxy/zaproxy…

Qt—模态与非模态对话框

Qt—模态与非模态对话框 核心概念 ​模态对话框​​&#xff1a;强制用户优先处理当前窗口&#xff0c;阻塞指定范围的用户交互。​非模态对话框​​&#xff1a;允许用户自由切换窗口&#xff0c;无交互限制。 一、模态对话框类型与行为 1. 应用级模态&#xff08;Applica…

Axure高保真CRM客户关系管理系统原型

一套出色的CRM&#xff08;客户关系管理&#xff09;系统&#xff0c;无疑是企业管理者掌控客户动态、提升销售业绩的得力助手。今天&#xff0c;就为大家介绍一款精心打造的Axure高保真CRM客户关系管理系统原型模板&#xff0c;助你轻松开启高效客户管理之旅。 这款CRM原型模…

【羊圈——状压 + DP / 记忆化搜索DP】

题目 一般DP代码&#xff08;注意&#xff0c;这里只能向外推(起始状态是f(1,0)&#xff0c;不能向内推&#xff08;不然会导致之前的羊圈被割裂&#xff09;&#xff09; #include <bits/stdc.h> using namespace std;const int MAX_N 210; const int MAX_M 16;int n…