Springboot按jar包方式启动,究竟发生了什么

Springboot按jar包方式启动,究竟发生了什么

一、背景

在工作的时候,我一直很好奇,当我们以Jar包的方式启动springboot工程时,jvm和springboot的jar工程之间究竟发生了什么样的关联反应,才能走到我们熟悉的业务代码main()函数里(也即下图所示的代码)

java 复制代码
    public static void main(String[] args) {  
        SpringApplication.run(FirstWebProjectApplication.class, args);  
    }

为了解答心中的疑惑,我跟踪了一下代码的运行。把得到的答案总结下来,方便和我同样有好奇心的朋友们查阅。

二、核心原理

1.FatJar和ThinJar(胖jar和瘦jar)

顾名思义,胖jar比瘦jar多了一些东西。

SpringBoot的胖jar的结构如下(并不是所有胖jar都是这种结构,这只是SpringBoot的约定俗成):

bash 复制代码
demo.jar 
├── BOOT-INF 
├── classes/ # 你写的代码 
│   └── lib/ # 所有第三方依赖 jar(几百个) 
├── META-INF 
│   └── MANIFEST.MF # Main-Class: JarLauncher 
└── org/ # SpringBoot 启动器(JarLauncher 所在包)

瘦jar的结构如下:

bash 复制代码
demo-thin.jar 
├── com/ # 仅你的代码 
└── META-INF 
    └── MANIFEST.MF # Main-Class: 你自己的启动类

从以上结构的对比就可以看出来,胖jar多了所有第三方依赖jar,并且包含了SpingBoot的启动器。

进一步,我们来看看实际的springboot工程和普通java工程打包出来的jar包有什么区别。

Springboot工程jar包

最简单的HelloWorld工程jar包

对比以上jar包结构对比,初步猜出jar工程运行的逻辑。

对于瘦jar,jvm先启动,JVM 读取 Jar 包内部的 META-INF/MANIFEST.MF文件,找到Main-Class,这个Main-Class,就是程序的入口。

对于Springboot的胖jar而言,在Main-Class和Start-Class之间增加了一个启动器。springboot的Jar包的启动流程就是,jvm先启动,JVM 读取 Jar 包内部的 META-INF/MANIFEST.MF文件,找到Main-Class,启动Launcher,Launcher启动Start-Class,后面就进入到我们熟悉的代码片段。

2.Springboot的启动器

JarLauncher.java的main()函数如下

java 复制代码
    public static void main(String[] args) throws Exception {  
        new JarLauncher().launch(args);  
    }

逻辑很简单,new一个JarLauncher的实例,然后调用实例的launch方法。

JarLauncher有一个父类ExecutableArchiveLaucher,这个父类先完成实例化,ExecutableArchiveLaucher的构造函数如下

java 复制代码
    public ExecutableArchiveLauncher() throws Exception {  
        this(Archive.create(Launcher.class));  
    }

    protected ExecutableArchiveLauncher(Archive archive) throws Exception {  
        this.archive = archive;  
        this.classPathIndex = getClassPathIndex(this.archive);  
    }

首先调用函数Archive.create(Launcher.class),得到Archive实例,赋值给JarLauncher实例的archive变量;

再调用getClassPathIndex(this.archive),将得到的ClassPathIndexFile的实例赋值给JarLauncher实例的classPathIndex变量;

最后调用JarLauncher实例的launch方法。

2.1 JarLauncher的this.archive成员变量

它实际上是JarFileArchive的实例,该实例里有一个成员变量JarFile,就是对springboot的jar包。

2.2 JarLauncher的classPathIndex成员变量

对于采用Jar包启动来说,classPathIndex为null。因为ClassPathIndex文件只在某些特殊场景(如-classpath模式)下才会生成,普通fat jar启动时没有这个文件。

2.3 JarLauncher()的launch()方法
graph TD %% 主干流程 Start(launch) --> IsExploded{是否已解压} IsExploded -->|是| E[通过 archive 获取<br>BOOT-INF/classes 和 BOOT-INF/lib 的 URL] IsExploded -->|否| D[配置 JVM 的 URL 协议处理器<br>注册嵌套 Jar 协议] D --> E E --> F["ClassLoader parent = getClass().getClassLoader();"<br>获取当前类的 ClassLoader 作为父加载器] F --> G[创建 LaunchedClassLoader<br>将上述 URL 作为类路径] G --> H[从 MANIFEST.MF 读取 Start-Class] H --> I["Thread.currentThread().setContextClassLoader(classLoader)"<br>设置线程上下文类加载器] I --> J[反射调用 Start-Class 的 main 方法]
2.4 为什么springboot工程的胖jar需要启动器

简单说,就是springboot工程的胖jar中这个jar包套jar包的结构,用默认的类加载器加载不了,所以要自定义一个LaunchedClassLoader去加载。Spring Boot 的胖 Jar 通过自定义类加载器 LaunchedClassLoader 打破了双亲委派机制,让嵌套在 BOOT-INF/lib 下的依赖 jar 能被正确加载。理解了这个过程,面试时被问到"Spring Boot 启动原理"就不再是死记硬背了。

相关推荐
桃西西呀5 小时前
Spring AI Alibaba 之七:我没写一行 tracing 埋点,Agent 每一步却被 Micrometer 看得清清楚楚
人工智能·spring·llm
桃西西呀5 小时前
Spring AI Alibaba 之八:我让 Agent 直接执行 shell 命令,它却读到了目录里的密钥,沙箱到底防住了什么
人工智能·spring·llm
~光~~7 小时前
【嵌入式linux学习】Cache 策略:Write-Through / Write-Back + Allocation
linux·学习·spring
m0_587383008 小时前
24 小时自助健身房系统软件开发实战指南与案例解析
java·spring boot·spring·系统架构·需求分析
m0_587383009 小时前
工业场景设备维修维护实战技巧 全流程标准化落地与常见问题排查指南
java·spring·小程序·架构·需求分析
木头科技10 小时前
【AI 工程化第八篇】Spring AI 多模型路由实战:DeepSeek、通义、Kimi、OpenAI 如何统一接入和自动切换
java·人工智能·spring
我是人✓11 小时前
Spring IOC注解全解析和Spring整合测试单元
java·后端·spring
ab12376811 小时前
Spring Integration 完整笔记 + 和 spring-integration-mqtt、Paho、EMQX 的关系
java·笔记·spring
HelloWorld工程师12 小时前
@Primary 注解
java·spring
代码什么用1 天前
Spring基础使用
java·后端·spring