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

news2024/9/24 3:22:46

大家都知道我们常用的 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 文件中有如图所示的一行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0v8Tm38d-1670339151215)(/Users/silence/Library/Application Support/typora-user-images/image-20221206214011822.png)]

可以看到这里的 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 项目是如何启动的,请把这篇文章转发给他。如果大家觉得我们的文章有帮助,欢迎点赞分享评论转发,一键三连。

本文由mdnice多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/67199.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redux使用详解(一) Redux的核心思想与基本使用

Redux 理解javascript纯函数 函数式编程中有一个非常重要的概念叫纯函数&#xff0c;JavaScript符合函数式编程的范式&#xff0c;所以也有纯函数的概念&#xff1b; 在react开发中纯函数是被多次提及的&#xff1b; 比如react中组件就被要求像是一个纯函数&#xff08;为什么…

使用STM32F103C8T自制freejoy控制板

1. 软件准备 1.1 STM公司的官方工具&#xff1a; STM32 ST-LINK Utility 已经更名为 STM32CubeProgrammer STSW-LINK004 - STM32 ST-LINK utility (replaced by STM32CubeProgrammer) - STMicroelectronics 1.2 FreeJoyConfiguratorQt V1.7.1 这个是刷好固件后的配置、调…

D-025 DP硬件电路设计

DP硬件电路设计1 简介1.1 DP接口分类1.2 DP接口和HDMI接口的区别1.3 DP接口的优势2 硬件层3 接口定义4 原理图设计1 简介 Display是一种新型的标准化的数字式视频接口标准&#xff0c;其支持的功能与HDMI相似&#xff0c;但是其目标是作为HDMI的补充&#xff0c;而非取代。DP …

红队渗透靶场之SickOs1.1

靶场考察知识 shellshock漏洞 shellshock即unix系统下的bash shell的一个漏洞, Bash 4.3以及之前的版本在处理某些构造的环境变量时存在安全漏洞, 向环境变量值内的函数定义后添加多余的字符串会触发此漏洞, 攻击者可利用此漏洞改变或绕过环境限制&#xff0c;以执行任意的sh…

Notepad++ ,json 、xml 格式化插件安装不了 和 github 网站访问不了 最佳解决方案

文章目录1.背景&#xff1a;2. 解决方法&#xff1a;方法一&#xff1a;修改hosts方法二&#xff1a; 通过 Watt Toolkit 加速1.简介&#xff1a;2.安装步骤&#xff1a;1.背景&#xff1a; 最近notpad 安装 JSON 和 xml 格式化工具安装不上&#xff0c;发现插件的地址 github…

docker之数据卷(Data Volumes)dockerfile

这里写目录标题宿主机与容器之间的文件拷贝数据卷数据卷容器Dockerfile自定义centos&#xff0c;具备vim及ifconfig作用&#xff08;体会学习Dockerfile的意义&#xff09;自定义tomcat8&#xff08;熟悉几乎所有的Dockerfile命令&#xff09;宿主机与容器之间的文件拷贝 引言…

故障分析 | Greenplum 集群 standby 故障处理

作者&#xff1a;杨文 DBA&#xff0c;负责客户项目的需求与维护&#xff0c;没有擅长&#xff0c;会点数据库&#xff0c;不限于MySQL、Redis、Cassandra、GreenPlum、ClickHouse、Elastic、TDSQL等等。 本文来源&#xff1a;原创投稿 *爱可生开源社区出品&#xff0c;原创内容…

SM4分组密码算法

对称加密算法SM4SM4算法介绍一、SM4加密流程二、轮函数F1.合成置换T3.非线性变换τ2.线性变换L4.加密的结果总结SM4算法介绍 SM4.0于2013年3月被列为国家密码行业标准“GM/T 0002-2012《SM4分组密码算法》&#xff08;原SMS4分组密码算法&#xff09;”。2016年被列入国家标准…

blender assetBrowser 资产浏览器

文章目录简介.基础操作打开资产浏览器.标记资产.资产库位置设置与加载其他工程的资产库.为资产设置分类.设置资产的属性.根据类型筛选资产.标记材质为资产.标记天空盒材质为资产.标记动作为资产.简介. 1 类似于unity的预制体&#xff0c;可以直接从资产浏览器里拖出来 2 创建时…

什么是缓存架构,什么是后端分布式多级缓存架构,全文解析带你了解其中门道

文章目录浏览器缓存客户端缓存CDN缓存反向代理缓存本地缓存分布式缓存其他&#xff1a;缓存命中率缓存问题&#xff1a;缓存穿透缓存问题&#xff1a;缓存击穿缓存问题&#xff1a;缓存雪崩缓存问题&#xff1a;缓存一致性缓存的其他问题开篇01数据库缓存1.1.MySQL查询缓存1.2.…

CAS登录认证

CAS最基本的协议过程&#xff1a; 名词解释 Ticket Grangting Ticket(TGT) &#xff1a; TGT是CAS为用户签发的登录票据&#xff0c;拥有了TGT&#xff0c;用户就可以证明自己在CAS成功登录过。TGT封装了Cookie值以及此Cookie值对应的用户信息。用户在CAS认证成功后&#xff0c…

应用于供暖、供水管道等场景的一种智能控制阀

智能控制阀&#xff0c;顾名思义就是能够实现智能化控制功能的一种控制阀。它有什么用处呢&#xff1f; TSM-04V无线智能控制阀由锂亚电池供电、超长续航&#xff0c;具有无线远程配置功能&#xff0c;是一种高可靠性阀控设备。自带断码显示屏&#xff0c;可以查看设备的电量、…

在VScode中使用Jupyter Notebook的一些技巧

目录 一、VScode中Jupyter Notebook的优点 二、Cell命令模式目前支持的Jupyter Notebook快捷 三、Cell编辑模式下支持的Vscode快捷键&#xff08;只描述与编辑相关的那些快捷键&#xff09;​​​​​​​ 一、VScode中Jupyter Notebook的优点 1.写py代码和使用Notebook经常…

DBCO-SS-Mal,DBCO-SS-Maleimide,马来酰亚胺衍生物试剂特点分析

一、基础产品数据&#xff08;Basic Product Data&#xff09;&#xff1a; CAS号&#xff1a;N/A 中文名&#xff1a;二苯并环辛炔-二硫键-马来酰亚胺 英文名&#xff1a;DBCO-SS-Maleimide&#xff0c;DBCO-SS-Mal二、详细产品数据&#xff08;Detailed Product Data&#xf…

aws 整理和理解aws的虚拟化技术

资料 Introduce_to_virtualizationKVM I/O虚拟化AWS EC2 Virtualization 2017: Introducing Nitro从AWS转向KVM&#xff0c;再看KVM与XEN的技术路线之争 简单整理并了解下和aws相关的虚拟化技术 虚拟化技术 相关概念 Hypervisor Hypervisor是一种运行在物理服务器和操作系…

一文彻底搞懂什么是SSH中间人攻击(Man-in-the-middle attack)

文章目录【1】背景【2】中间人攻击原理登录报错信息提示从首次登录服务器说起中间人攻击原理重装服务器导致ssh无法登录的解决方案【3】如何避免中间人攻击&#xff1f;【4】什么是Known_hosts &#xff1f;known_hosts中的文件来自哪里&#xff1f;这个/etc/ssh/ssh_host_ecds…

gcc内联汇编

1. 介绍 用汇编编写的程序虽然运行速度快&#xff0c;但开发速度非常慢&#xff0c;效率也很低。如果只是想对关键代码段进行优化&#xff0c;或许更好的办法是将汇编指令嵌入到 C 语言程序中&#xff0c;从而充分利用高级语言和汇编语言各自的特点。但一般来讲&#xff0c;在…

华为机试_HJ81 字符串字符匹配【简单】【收藏!】

目录 描述 输入描述&#xff1a; 输出描述&#xff1a; 解题过程 提交代码 学习代码 收藏点 一、strstr函数 二、stdlib.h 描述 判断短字符串S中的所有字符是否在长字符串T中全部出现。 请注意本题有多组样例输入。 数据范围:1≤len(S),len(T)≤200 进阶&#xf…

如何解决“德语/文”等外文字符显示乱码问题

目录 1“德文/语字符”等外文显示乱码原因 2 如何解决“德文/语字符”等外文显示乱码 2.1 使用支持多个“编码”切换的工具打开该文本 2.2 使用变音字符的标准ASCII字符 2.3 将计算机系统切换至西欧编码 3 结尾 1“德文/语字符”等外文显示乱码原因 以德文/语为例&#…

vue3(二)

前一篇讲了 vue3的生命周期钩子的使用。 本节接着讲 vue3的数据通信。 provide/inject 依赖注入 App.vue <script setup lang"ts"> import { ref, provide } from vue import List from ./components/List.vue import User from ./components/User.vue impo…