当我们的执行 java -jar xxx.jar 的时候底层到底做了什么?

大家都知道我们常用的 SpringBoot 项目最终在线上运行的时候都是通过启动 java -jar xxx.jar 命令来运行的。

那你有没有想过一个问题,那就是当我们执行 java -jar 命令后,到底底层做了什么就启动了我们的 SpringBoot 应用呢?

或者说一个 SpringBoot 的应用到底是如何运行起来的呢?今天阿粉就带大家来看下。

认识 jar

在介绍 java -jar 运行原理之前我们先看一下 jar 包里面都包含了哪些内容,我们准备一个 SpringBoot 项目,通过在 https://start.spring.io/ 上我们可以快速创建一个 SpringBoot 项目,下载一个对应版本和报名的 zip 包。

下载后的项目我们在 pom 依赖里面可以看到有如下依赖,这个插件是我们构建可执行 jar 的前提,所以如果想要打包成一个 jar 那必须在 pom 有增加这个插件,从 start.spring.io 上创建的项目默认是会带上这个插件的。

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

接下来我们执行 mvn package,执行完过后在项目的 target 目录里面我们可以看到有如下两个 jar 包,我们分别把这两个 jar 解压一下看看里面的内容,.original 后缀的 jar 需要把后面的 .original 去掉就可以解压了。jar 文件的解压跟我们平常的 zip 解压是一样的,jar 文件采用的是 zip 压缩格式存储,所以任何可以解压 zip 文件的软件都可以解压 jar 文件。

解压过后,我们对比两种解压文件,可以发现,两个文件夹中的内容还是有很大区别的,如下所示,左侧是 demo-jar-0.0.1-SNAPSHOT.jar 右侧是对应的 original jar

其中有一些相同的文件夹和文件,比如 META-INFapplication.properties 等,而且我们可以明显的看到左侧的压缩包中有项目需要依赖的所有库文件,存放于 lib 文件夹中。

所以我们可以大胆的猜测,左侧的压缩包就是 spring-boot-maven-plugin 这个插件帮我们把依赖的库以及相应的文件调整了一下目录结构而生成的,事实其实也是如此。

java -jar 原理

首先我们要知道的是这个 java -jar 不是什么新的东西,而是 java 本身就自带的命令,而且java -jar 命令在执行的时候,命令本身对于这个 jar 是不是 SpringBoot 项目是不感知的,只要是符合 Java 标准规范的jar 都可以通过这个命令启动。

而在 Java 官方文档显示,当 -jar 参数存在的时候,jar 文件资源里面必须包含用 Main-Class 指定的一个启动类,而且同样根据规范这个资源文件 MANIFEST.MF 必须放在 /META-INF/ 目录下。对比我们上面解压后的文件,可以看到在左侧的资源文件 MANIFEST.MF 文件中有如图所示的一行。

[图片上传失败...(image-3db0a9-1670339533552)]

可以看到这里的 Main-Class 属性配置的是 org.springframework.boot.loader.JarLauncher,而如果小伙伴更仔细一点的话,会发现我们项目的启动类也在这个文件里面,是通过 Start-Class 字段来表示的,Start-Class 这个属性不是 Java 官方的属性。

由此我们先大胆的猜测一下,当我们在执行java -jar 的时候,由于我们的 jar 里面存在 MANIFEST.MF 文件,并且其中包含了 Main-Class 属性且配置了 org.springframework.boot.loader.JarLauncher 类,通过调用 JarLauncher 类结合 Start-Class 属性引导出我们项目的启动类进行启动。接下来我们就通过源码来验证一下这个猜想。

因为 JarLauncher 类是在 spring-boot-loader 模块,所以我们在 pom 文件中增加如下依赖,就可以下载源码进行跟踪了。

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-loader</artifactId>
            <scope>provided</scope>
</dependency>

通过源码我们可以看到 JarLauncher 类的代码如下

package org.springframework.boot.loader;

import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.Archive.EntryFilter;

public class JarLauncher extends ExecutableArchiveLauncher {

    static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
        if (entry.isDirectory()) {
            return entry.getName().equals("BOOT-INF/classes/");
        }
        return entry.getName().startsWith("BOOT-INF/lib/");
    };

    public JarLauncher() {
    }

    protected JarLauncher(Archive archive) {
        super(archive);
    }

    @Override
    protected boolean isPostProcessingClassPathArchives() {
        return false;
    }

    @Override
    protected boolean isNestedArchive(Archive.Entry entry) {
        return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
    }

    @Override
    protected String getArchiveEntryPathPrefix() {
        return "BOOT-INF/";
    }

    public static void main(String[] args) throws Exception {
        new JarLauncher().launch(args);
    }

}

其中有两个点我们可以关注一下,第一个是这个类有一个 main 方法,这也是为什么 java -jar 命令可以进行引导的原因,毕竟 java 程序都是通过 main 方法进行运行的。其次是这里面有两个路径 BOOT-INF/classes/BOOT-INF/lib/这两个路径正好是我们的源码路径和第三方依赖路径。

JarLauncher 类里面的 main() 方法主要是运行 Launcher 里面的 launch() 方法,这几个类的关系图如下所示

跟着代码我们可以看到最终调用的是这个 run() 方法

而这里的参数 mainClasslaunchClass 都是通过通过下面的逻辑获取的,都是通过资源文件里面的 Start-Class 来进行获取的,这里正是我们项目的启动类,由此可以看到我们上面的猜想是正确的。

扩展

上面的类图当中我们还可以看到除了有 JarLauncher 以外还有一个 WarLauncher 类,确实我们的 SpringBoot 项目也是可以配置成 war 进行部署的。我们只需要将打包插件里面的 jar 更换成 war 即可。大家可以自行尝试重新打包解压进行分析,这里 war 包部署方式只研究学习就好了,SpringBoot 应用还是尽量都使用 Jar 的方式进行部署。

总结

通过上面的内容我们知道了当我们在执行 java -jar 的时候,根据 java 官方规范会引导 jar 包里面 MANIFEST.MF 文件中的 Main-Class 属性对应的启动类,该启动类中必须包含 main() 方法。

而对于我们 SpringBoot 项目构建的 jar 包,除了 Main-Class 属性外还会有一个 Start-Class 属性绑定的是我们项目的启动类,当我们在执行 java -jar 的时候优先引导的是 org.springframework.boot.loader.JarLauncher#main 方法,该方法内部会通过引导 Start-Class 属性来启动我们的应用代码。

通过上面的分析相比大家对于 SpringBoot 是如何通过 java -jar 进行启动了有了一个详细的了解,下次再有人问你 SpringBoot 项目是如何启动的,请把这篇文章转发给他。如果大家觉得我们的文章有帮助,欢迎点赞分享评论转发,一键三连。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容