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 启动原理"就不再是死记硬背了。

相关推荐
所愿ღ3 小时前
SSM框架-Spring1
java·开发语言·笔记·spring
Flittly4 小时前
【SpringSecurity新手村系列】(5)RBAC角色权限与账户状态校验
java·spring boot·笔记·安全·spring·ai
所愿ღ4 小时前
SSM框架-Spring2
java·开发语言·笔记·spring
Flittly4 小时前
【SpringSecurity新手村系列】(6)基于角色的权限控制、权限拦截注解与自定义无权限页面
java·spring boot·安全·spring·安全架构
weixin_ab5 小时前
【使用 curl 快速验证 Maven 依赖是否存在 —— 以 Spring AI OpenAI Starter 为例】
spring·maven
派大星酷5 小时前
AOP 完整精讲:原理、核心概念、五种通知、切点语法、自定义注解实战
java·mysql·spring
杨凯凡14 小时前
【021】反射与注解:Spring 里背后的影子
java·后端·spring
riNt PTIP14 小时前
SpringBoot创建动态定时任务的几种方式
java·spring boot·spring
invicinble15 小时前
spirng的bean的生命周期,以及为什么这么设计
spring