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

SpringBoot打包运行原理和加载机制原理

文章目录

  • 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 BootjarFat JAR 肥包, 不是 Thin JAR

  • Fat JAR(如Spring Boot可执行JAR)则内嵌 所有 依赖 Jar 包,可直接运行。
  • Thin JAR 仅包含项目代码和 依赖 Jar 包 的清单文件,依赖需额外配置;

Spring BootjarFat 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/lib
    • Spring 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的区别如下:

比较维度‌普通 JAR‌(thin JAR)标准 Fat Jar‌‌Spring 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 拥有三套独立的生命周期(cleandefaultsite),每个生命周期包含多个顺序执行的阶段(Phase
插件的目标(Goal)需绑定到具体阶段,才能在构建过程中触发执行。
spring-boot-maven-plugin 通常绑定到 default 生命周期的 package 阶段,其核心目标是 repackage(重新打包)。

配置示例如下:

<plugin>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-maven-plugin</artifactId>  <executions>  <execution>  <goals>  <goal>repackage</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. 过滤项目运行时依赖(排除测试依赖等)  Set<Artifact> 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 打破双亲委派

SpringBootLaunchedURLClassLoader通过重写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 资源加载

SpringBootJarURLConnection通过扩展URLConnection实现嵌套JAR资源加载,核心机制包括:

  • 自定义协议处理器解析jar:file:/xxx.jar!/BOOT-INF/lib/nested.jar!/格式路径;
  • 使用JarFileJarEntry逐层解包嵌套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/classesBOOT-INF/lib/*下的资源
    • 注册自定义JarURL协议处理器,支持嵌套JAR路径解析(如jar:file:/app.jar!/BOOT-INF/lib/nested.jar!/
  • 反射启动应用‌
    • 通过MANIFEST.MFStart-Class定位用户主程序(如com.example.Application)
    • 反射调用用户类的main()方法,移交控制权至SpringApplication启动流程

关键步骤时序:
JVMJarLauncher.main()LaunchedURLClassLoader加载 → 反射调用Start-ClassSpringApplication.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应用可以像普通可执行文件一样运行,简化了部署和分发过程。

相关文章:

  • 篇章六 论坛系统——业务开发——实现业务功能
  • JUC核心解析系列(四)——同步工具类 (Synchronizers)深度解析
  • Pytorch 卷积神经网络参数说明一
  • OSGI 是什么,有哪些具体应用、java8、9、10、11比较
  • uni-app项目实战笔记11--定义scss颜色变量方便页面引用
  • HarmonyOS 组件复用 指南
  • [直播推流] 使用 librtmp 库推流
  • 开心灿烂go开发面试题
  • C++/OpenCV地砖识别系统结合 Libevent 实现网络化 AI 接入
  • 雪豹速清APP:高效清理,畅享流畅手机体验
  • 【C++进阶篇】哈希的应用(位图)
  • 代码随想录算法训练营day4
  • Verilog自适应位宽与指定位宽不同导致模块无法正常执行
  • CMake 构建系统概述
  • CAD中DWG到DXF文件解析(一)
  • Linux入门(十六)shellshell变量设置环境变量位置参数变量预定义变量
  • langchain_mcp_adapters - MultiServerMCPClient 获取工具
  • 全球化2.0|云轴科技ZStack联合Teleplex举办技术沙龙 · 吉隆坡站
  • Flask文件上传与异常处理完全指南
  • 【Qt】QStateMachine状态机-对状态机分组、历史状态,实现复杂状态机
  • 门户网站建设自查整改/登封网站关键词优化软件
  • seo网站推广seo/网盘资源大全
  • 网站建设浏览器不兼容/搜索引擎技术优化
  • 墙绘做网站推广有作用没/淘宝联盟怎么推广
  • 网站怎么做免费推广方案/义乌百度广告公司
  • 建设银行安徽分行招聘网站/南昌seo