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

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

相关推荐
fengxin_rou16 小时前
Feed 三级缓存架构详解:分层设计、缓存一致性与高性能实战
spring·缓存·架构
Cyan_RA918 小时前
SpringMVC 数据格式化处理 详解
java·开发语言·spring·mvc·ssm·springmvc·数据格式化
橘子海全栈攻城狮18 小时前
【最新源码】基于springboot的快递物流平台的设计与实现C102
java·开发语言·spring boot·后端·spring·web安全
huaiixinsi18 小时前
Canal + Outbox、Kafka 选型与高可用、Caffeine 底层原理总结
java·数据库·分布式·mysql·spring·adb·kafka
Ting-yu18 小时前
SpringCloud快速入门(11)---- Sentinel(异常处理)
java·spring boot·后端·spring·spring cloud·sentinel
Ting-yu19 小时前
SpringCloud快速入门(10)---- Sentinel(应用场景&控制台安装)
spring·spring cloud·sentinel
贫民窟的勇敢爷们1 天前
SpringBoot整合AOP切面编程实战,实现日志统一记录+接口权限校验
java·spring boot·spring
灵犀学长1 天前
基于 Spring ThreadPoolTaskScheduler + CronTrigger 实现的动态定时任务调度系统
java·数据库·spring
吾疾唯君医1 天前
Java SpringBoot集成积木报表实操记录
java·spring boot·spring·导出excel·积木报表·数据文件下载
云烟成雨TD1 天前
Spring AI Alibaba 1.x 系列【52】Interrupts 中断机制:节点执行前后静态中断
java·人工智能·spring