做网站网站的人是怎么被抓的,西安网站制作,南通注册公司,深圳网站建设十强文章目录 1 SpringBoot打Jar包运行原理1.1 Spring boot的 jar可以直接运行1.2 Springboot Fat JAR目录结构1.3 Jar 启动入口: MANIFEST.MF文件1.4 SpringBoot 如何打包1.4.1 Maven 生命周期与插件目标的绑定1.4.2 repackage阶段核心逻辑1.4.3 Repackager 与文件布局#xff08… 文章目录 1 SpringBoot打Jar包运行原理1.1 Spring boot的 jar可以直接运行1.2 Springboot Fat JAR目录结构1.3 Jar 启动入口: MANIFEST.MF文件1.4 SpringBoot 如何打包1.4.1 Maven 生命周期与插件目标的绑定1.4.2 repackage阶段核心逻辑1.4.3 Repackager 与文件布局Layout 2 类加载机制2.1 JVM 默认的加载机制双亲委派。2.2 SpringBoot 打破双亲2.2.1 Springboot 优先使用自己的内嵌包2.2.2 核心源码LaunchedURLClassLoader 打破双亲委派2.2.3 JarURLConnection 嵌套 Jar 资源加载2.2.4 JarLauncher 启动过程2.2.5 Spring Boot应用 启动过程 总结 1 SpringBoot打Jar包运行原理
点击了解maven打包操作
1.1 Spring boot的 jar可以直接运行
Spring Boot的jar 是 Fat JAR 肥包 不是 Thin JAR 。
Fat JAR如Spring Boot可执行JAR则内嵌 所有 依赖 Jar 包可直接运行。Thin JAR 仅包含项目代码和 依赖 Jar 包 的清单文件依赖需额外配置
Spring Boot的 jar 是 Fat JAR 肥包 可以直接运行已经包含了嵌入式Web服务器和打包了所有依赖项的可执行JAR结构。 所以Spring Boot的jar 很大的动不动 几百M。
Spring Boot的jar 是 Fat JAR 本质执行流程 java -jar app.jar ↓ JVM执行JarLauncher.main() ↓ 加载BOOT-INF/lib中所有依赖 ↓ 启动嵌入式Tomcat默认端口8080 ↓ 执行SpringBootApplication主类 Spring Boot的jar 是 Fat JAR 设计 使Spring Boot应用具有开箱即运行的特性简化了部署 的依赖管理。
1.2 Springboot Fat JAR目录结构
Springboot jar 的 4个组成部分
特殊JAR结构使用Spring Boot Maven/Gradle插件打包时会生成一个包含三个主要部分的fat JAR 应用代码在BOOT-INF/classes所有依赖库在BOOT-INF/libSpring Boot启动加载器在org/springframework/boot/loader 嵌入式服务器 JAR内置了Tomcat/Jetty等Web服务器无需外部应用服务器自定义类加载器 通过JarLauncher启动时使用LaunchedURLClassLoader加载依赖MANIFEST.MF 配置 指定了 Main-Classorg.springframework.boot.loader.JarLauncher和Start-Class主应用类与普通 JAR由 maven-jar-plugin 生成相比Fat JAR 新增了两部分关键内容 BOOT-INF/lib 目录存放项目所有 Maven 依赖的 JAR 包如 spring-boot-starter-web、jackson-databind 等。Spring Boot Loader 类位于 org.springframework.boot.loader 包下包含自定义启动器和类加载器解决嵌套 JAR 的加载问题。
spring boot fat jar目录结构如下
spring boot fat jar和普通jar的区别如下
比较维度普通 JARthin JAR标准 Fat JarSpring Boot Fat Jar依赖包含方式仅包含项目自身的编译代码和资源文件不包含依赖库运行时需手动配置类路径添加依赖包含项目自身代码及所有依赖库依赖字节码平铺至根目录自包含运行依赖库存储在 BOOT-INF/lib/ 目录下通过自定义类加载器加载可执行性若无主类声明则不可直接执行需通过 java -cp 指定主类和依赖路径可声明主类通过 MANIFEST.MF直接通过 java -jar 运行内嵌 SpringApplication 启动类支持 java -jar 直接运行结构差异标准结构仅含 META-INF/ 和项目类文件目录依赖与项目代码混合在根目录平铺结构含专属目录BOOT-INF/classes/项目代码、BOOT-INF/lib/依赖库服务器支持不包含服务器Web应用需部署至外部服务器如Tomcat通常不包含服务器需自行处理内嵌服务器如Tomcat/Jetty无需外部部署适用场景作为库文件供其他项目依赖独立应用分发简化依赖管理微服务或独立Spring应用的一键部署ClassPath生成逻辑依赖路径由用户显式指定依赖加载顺序由文件系统平铺结构决定依赖按 BOOT-INF/lib/ 内JAR的Entry顺序加载
1.3 Jar 启动入口: MANIFEST.MF文件
MANIFEST.MF文件 至关重要是spring boot 的启动入口文件 在Spring Boot的可执行Jar包Fat Jar中META-INF目录下的MANIFEST.MF文件扮演着至关重要的角色。 MANIFEST.MF 文件包含了Jar包的元数据用于指导Java虚拟机JVM如何加载和运行Jar包。
以下是对MANIFEST.MF文件内容
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: tms-start
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.sean.StartApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.4.5
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher关键属性
Main-Class指向 Spring Boot 内置的 JarLauncher它是 JAR 执行的入口。Start-Class指向应用主类如 com.sean.StartApplication由 JarLauncher 反射调用其 main 方法启动应用。Spring-Boot-Classpath-Index类路径索引文件优化启动速度Spring-Boot-Layers-Index用于分层 Docker 镜像构建
属性详细介绍
Spring-Boot-Version: 2.4.5表示该Jar包是基于Spring Boot 2.4.5版本构建的。Main-Class: org.springframework.boot.loader.JarLauncher指定了Jar包的入口类。当Jar包被运行时JVM会首先执行这个类的main方法。在Spring Boot中JarLauncher 负责加载和启动应用程序。Start-Class: com.sean.StartApplication指定了Spring Boot应用程序的实际入口类。JarLauncher在启动时会使用反射调用这个类的main方法。Spring-Boot-Classes: BOOT-INF/classes/指定了应用程序类文件的存放路径。这些类文件是由项目源代码编译生成的。Spring-Boot-Lib: BOOT-INF/lib/指定了应用程序依赖的Jar包文件的存放路径。这些依赖是在构建过程中被复制到Jar包中的。Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx可选用于优化类加载性能通过索引文件来加速类路径的查找。Spring-Boot-Layers-Index: BOOT-INF/layers.idx可选在Spring Boot 2.3及以上版本中引入用于支持构建分层Jar包以便在构建和运行时进行优化。
1.4 SpringBoot 如何打包
SpringBoot 是打包的 fat jar 可以直接运行那spring boot是如何打包的 fat jar和普通jar结构有什么区别 要理解 Fat JAR 的生成过程需先掌握 spring-boot-maven-plugin 这一核心插件的工作逻辑。作为 Maven 自定义插件它通过绑定 Maven 生命周期的特定阶段Phase实现对 JAR 包的重新打包Repackage
1.4.1 Maven 生命周期与插件目标的绑定
Maven 拥有三套独立的生命周期clean、default、site每个生命周期包含多个顺序执行的阶段Phase 插件的目标Goal需绑定到具体阶段才能在构建过程中触发执行。 spring-boot-maven-plugin 通常绑定到 default 生命周期的 package 阶段其核心目标是 repackage重新打包。
配置示例如下
plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId executions execution goals goalrepackage/goal !-- 绑定 repackage 目标 -- /goals /execution /executions
/plugin 1.4.2 repackage阶段核心逻辑
repackage 目标的执行入口是 org.springframework.boot.maven.RepackageMojo#execute其核心逻辑如下
private void repackage() throws MojoExecutionException { // 1. 获取原始 JAR由 maven-jar-plugin 生成最终重命名为 .original Artifact source getSourceArtifact(); // 2. 定义最终输出的 Fat JAR 文件 File target getTargetFile(); // 3. 创建 Repackager负责实际打包逻辑 Repackager repackager getRepackager(source.getFile()); // 4. 过滤项目运行时依赖排除测试依赖等 SetArtifact artifacts filterDependencies(this.project.getArtifacts(), getFilters(getAdditionalFilters())); // 5. 将依赖转换为 Libraries 对象统一管理依赖 Libraries libraries new ArtifactsLibraries(artifacts, this.requiresUnpack, getLog()); try { // 6. 生成启动脚本可选 LaunchScript launchScript getLaunchScript(); // 7. 执行重新打包生成 Fat JAR repackager.repackage(target, libraries, launchScript); } catch (IOException ex) { throw new MojoExecutionException(ex.getMessage(), ex); } // 8. 更新原始 JAR 为 .original 后缀 updateArtifact(source, target, repackager.getBackupFile());
} 1.4.3 Repackager 与文件布局Layout
Repackager 是实际执行打包的核心类其初始化时会通过 layoutFactory 定义文件布局即 JAR 内部目录结构。 关键代码如下
private Repackager getRepackager(File source) { Repackager repackager new Repackager(source, this.layoutFactory); repackager.addMainClassTimeoutWarningListener(new LoggingMainClassTimeoutWarningListener()); //设置main class的名称如果不指定的话则会查找第一个包含main方法的类repacke最后将会设置org.springframework.boot.loader.JarLauncherrepackager.setMainClass(this.mainClass); if (this.layout ! null) { getLog().info(Layout: this.layout); //重点关心下layout 最终返回了 org.springframework.boot.loader.tools.Layouts.Jarrepackager.setLayout(this.layout.layout()); } return repackager;
} layout 定义了 JAR 的目录结构规范。 以 Layouts.Jar 为例对应 Fat JAR
/** Executable JAR layout.
*/
public static class Jar implements RepackagingLayout { Override public String getLauncherClassName() { return org.springframework.boot.loader.JarLauncher; // 启动类} Override public String getLibraryDestination(String libraryName, LibraryScope scope) { return BOOT-INF/lib/; // 依赖 JAR 存放路径 } Override public String getClassesLocation() { return ; } Override public String getRepackagedClassesLocation() { return BOOT-INF/classes/; // 应用类存放路径 } Override public boolean isExecutable() { return true; }
} 2 类加载机制
2.1 JVM 默认的加载机制双亲委派。
点击了解JVM类加载机制
2.2 SpringBoot 打破双亲
由于JVM 标准三层类加载器均不原生支持内嵌包加载需通过自定义类加载器实现。 所以Springboot 自定义了 自己的的 内嵌包加载器 LaunchedURLClassLoader 加载 FarJar 里边的内嵌包而且Springboot 需要打破双亲委派 优先使用自己的内嵌包而不是 Java类路径下的 公共包。
2.2.1 Springboot 优先使用自己的内嵌包
为啥 Springboot 需要 优先使用自己的内嵌包而不是 Java类路径下的 公共包有两个原因
Java类路径下的 公共包 可能和 Far 里边的 内嵌包 存在 版本上的不同 需要优先使用 自己的版本而不是公共的版本。不同的 SpringBoot 需要支持不同模块或插件加载相同类的不同版本 , 优先加载 内嵌包 可独立管理依赖实现版本隔离 以及实现 模块化隔离但是 Fat JAR 包含嵌套的依赖 JAR如 BOOT-INF/lib/*.jar而 Java 原生类加载器无法直接加载嵌套 JAR
SpringBoot 通过自定义类加载器和扩展 JAR 协议解决了这一问题。 Springboot loader 目录下的 自定义启动器和类加载器 org.springframework.boot.loader.JarLauncher实现了下面两个功能
解决 内嵌 JAR包 的加载问题打破双亲委派模式
因为spring boot jar打包含专属目录BOOT-INF/classes/项目代码、BOOT-INF/lib/依赖库假设系统采用Spring Boot打包
express-locker.jar
├── BOOT-INF/
│ ├── classes/ (主程序)
│ └── lib/
│ ├── sms-service.jar (短信服务)
│ └── payment.jar (支付模块)当主程序需要加载sms-service.jar里的短信模板或者某个类时这时资源类访问地址大致如下URL url new URL(jar:file:express-locker.jar!/BOOT-INF/lib/sms-service.jar!/templates/alert.txt); 这里就需要打破双亲委派同时要能对这种自定义协议的url进行嵌套加载
2.2.2 核心源码LaunchedURLClassLoader 打破双亲委派
SpringBoot的LaunchedURLClassLoader通过重写loadClass方法打破双亲委派机制优先加载BOOT-INF/下的嵌套JAR资源。 其加载顺序为
当前加载器扫描嵌套jar失败后委托父加载器。
这种设计实现了独立可执行JAR的模块化加载解决了传统委派模式无法访问嵌套依赖的问题是Spring Boot Fat Jar运行的核心机制。
public class LaunchedURLClassLoader extends URLClassLoader {// 特殊处理的包前缀private static final String[] DEFAULT_HIDDEN_PACKAGES new String[] {java., javax., jakarta., org.springframework.boot.loader. };// 覆盖的类加载逻辑Overrideprotected Class? loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 1、 检查是否已加载Class? loadedClass findLoadedClass(name);if (loadedClass ! null) {return loadedClass;}// 2. 检查是否在隐藏包中if (isHidden(name)) {return super.loadClass(name, resolve);}// 3. 尝试从BOOT-INF/classes加载try {Class? localClass findClass(name);if (localClass ! null) {return localClass;}} catch (ClassNotFoundException ex) {}// 4. 尝试从父加载器加载try {return Class.forName(name, false, getParent());} catch (ClassNotFoundException ex) {}// 5. 最后尝试从BOOT-INF/lib加载return findClass(name);}}private boolean isHidden(String className) {for (String hiddenPackage : DEFAULT_HIDDEN_PACKAGES) {if (className.startsWith(hiddenPackage)) {return true;}}return false;}
}2.2.3 JarURLConnection 嵌套 Jar 资源加载
SpringBoot 的JarURLConnection通过扩展URLConnection实现嵌套JAR资源加载核心机制包括
自定义协议处理器解析jar:file:/xxx.jar!/BOOT-INF/lib/nested.jar!/格式路径使用JarFile和JarEntry逐层解包嵌套JAR配合LaunchedURLClassLoader实现资源定位。
该设计解决了传统URL协议无法访问多层嵌套JAR的问题是FatJar运行的基础支撑。
public class JarURLConnection extends java.net.JarURLConnection {private JarFile jarFile;public InputStream getInputStream() throws IOException {// 处理嵌套jar的路径格式jar:nested:/path.jar!/nested.jar!/String nestedPath getEntryName();if (nestedPath.contains(!/)) {String[] parts nestedPath.split(!/, 2);JarFile outerJar new JarFile(parts[0]);JarEntry nestedEntry outerJar.getJarEntry(parts[1]);return new NestedJarInputStream(outerJar, nestedEntry);}return super.getInputStream();}
}2.2.4 JarLauncher 启动过程
以下是 Spring Boot JarLauncher 启动的大致步骤
JVM入口调用 执行 java -jar 命令触发MANIFEST.MF中指定的Main-Class: org.springframework.boot.loader.JarLauncher初始化JarLauncher实例并调用其main()方法类加载器构建 创建LaunchedURLClassLoader优先加载BOOT-INF/classes和BOOT-INF/lib/*下的资源注册自定义JarURL协议处理器支持嵌套JAR路径解析如jar:file:/app.jar!/BOOT-INF/lib/nested.jar!/ 反射启动应用 通过MANIFEST.MF的Start-Class定位用户主程序如com.example.Application反射调用用户类的main()方法移交控制权至SpringApplication启动流程
关键步骤时序 JVM → JarLauncher.main() → LaunchedURLClassLoader加载 → 反射调用Start-Class → SpringApplication.run() 该流程通过打破双亲委派实现嵌套JAR资源加载确保FatJar自包含运行 执行java -jar时JVM读取MANIFEST.MF中的Main-Class通常是org.springframework.boot.loader.JarLauncher JarLauncher 核心代码
public class JarLauncher extends ExecutableArchiveLauncher {Overrideprotected String getMainClass() throws Exception {// 从 MANIFEST.MF 读取 Start-Classreturn getArchive().getManifest().getMainAttributes().getValue(Start-Class);}public static void main(String[] args) throws Exception {new JarLauncher().launch(args);}
}2.2.5 Spring Boot应用 启动过程 总结
java -jar启动过程中会首先Paths类找到对应的启动jar的位置信息。读取mainfest.mf文件里面的Main-class启动JarLauncher使用LaunchedURLClassLoader类加载器完成对当前类依赖的加载如果当前类不存在则去super打破了双亲委派。LaunchedURLClassLoader类加载器通过使用JarURLConnection解决嵌套 JAR 的加载问题加载主类start-class并且执行main方法。
这种设计使得Spring Boot应用可以像普通可执行文件一样运行简化了部署和分发过程。