为什么SpringBoot可以直接运行 Jar 包?看完这篇文章我瞬间秒懂
lipiwang 2024-11-19 06:43 12 浏览 0 评论
小伙伴们好呀,今天咋们来探索下,为什么 SpringBoot 的 jar 包可以直接运行? 以及 4ye 踩到的坑
目录如下
开始之前,先简单介绍下这个 jar
什么是 jar
JAR 文件格式以流行的 ZIP 文件格式为基础。与 ZIP 文件不同的是,JAR 文件不仅用于压缩和发布,而且还用于部署和封装库、组件和插件程序,并可被像编译器和 JVM 这样的工具直接使用。在 JAR 中包含特殊的文件,如 manifests 和部署描述符,用来指示工具如何处理特定的 JAR。——《百度百科》
jar包结构图
这里小伙伴们可以自行查找下 jar文件规范
例如 https://blog.51cto.com/robinc/547658
规范中最重要的一点,就是 MATE-INF 文件夹中的 MANIFEST.MF 清单文件了。
文件内容如下
一眼看过去,这个 Main-Class 配置就特别突出了。 它指明了这个启动类的位置。
当我们用 java -jar xx.jar 命令运行一个 jar 包时,无外乎,它肯定是帮我们找到这个 main 方法,然后启动它。
(ps:使用 -jar 时,会忽略 classpath 环境的配置。)
这里我将上期 Map 专题的代码进行打包,运行效果如下
可以看到第一次运行时出现没有主清单属性的提示 。
在 pom 文件中添加 Main-class 配置(如下),即可解决问题。
复制<build>
<finalName>Map</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>cn.java4ye.HashMapMain</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-a-jar</id>
<phase>compile</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.
那么,到这里,基本的秘密已经被我们知道了。
执行 java -jar xx.jar 命令时,会去:
解析 MATE-INF 文件夹中的MANIFEST.MF 清单文件,
然后找到 Main-class ,运行其中的 main方法。
接着我们再反过来看看这个 Springboot 的 jar 包有啥不同。
官方文档参考
建议大家先去读读这个文档~
地址:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#executable-jar
没看文档前,我想得很简单,直接就打开 Springboot 打包好的 jar 包去找 META-INF 文件夹下的 MANIFEST.MF
看到后,我的理解如下图
其他配置应该是表明这个 classes ,lib 去哪里找。
实际上呢,这个理解也没有错,但是里面多了很多细节~
比如:
- 嵌套的 jar 包要怎么解决
- classpath.idx 和 layers.idx 是用来干嘛的
- jarmode 又是什么
下面就让我们来了解探索下它的奥秘叭。(也顺便看看 4ye 掉到怎样的牛角尖去了叭)
META-INF
我们可以看到,Springboot 插件打包后生成的 jar 包和原来是有很大的不同的。
除了 META-INF 文件夹结构没变化之外。
BOOT-INF
结构如下
这里的重点在 classpath.idx 和 layers.idx 这两个索引文件。
classpath.idx 可以被 jar 和 war 包使用,它配置了哪些 jar 包要被加载到 classpath 中。
layers.idx 只能被 jar 包使用,在 创建镜像 的时候被使用,如 Docker/OCI (OCI是一种容器标准)
原话如下:
The layers.idx file can be used only for jars, and it allows a jar to be split into logical layers for Docker/OCI image creation.
官方文档中详细说明了这个 layers.idx 的规范。
比如 "dependencies" 是这部分 layer 的名称,下面的都属于这个 layer。
org
这里就是 Springboot 运行 jar 包的秘密了。
org文件夹
这里先介绍下一些背景,然后再简单看看源码~
背景一
Fat Jar 指的就是这种 jar in jar , 或者说嵌套的 Jar 包。但是 Java 中并没有能加载嵌套 Jar 的方式,所以 Spring boot 自己写了这套代码,来解决这个问题。
当然,这句话是从 Springboot 官方文档中发现的
Java does not provide any standard way to load nested jar files (that is, jar files that are themselves contained within a jar).
到了这里,我就掉入一个坑了。。 因为对这块不熟悉,我一直以为它说的就是 URLClassLoader 无法加载到这里面的class,可是我测试了好多遍,发现嵌套在里面的 class 可以被加载到呀(自己挖的坑,文末解答)
我翻了很多资料,还去查看 GitHub 的 issue,发现都没有人提过相关的问题
到了这里,我已经非常非常无奈了!这痛苦的感觉,就像刚开始学习编程时,装环境被各种奇奇怪怪的 bug 搞到渐渐没脾气。
被折磨了 N 久之后,我又找了另外一个角度,难道它说的是这个 JarFile 。我的天,我尝试了一下后,发现确实没有办法加载嵌套的 Jar
证明如下
背景二
URLStreamHandler ,它是用来处理各种协议的(比如 http,file,jar 等等),配合 URL,URLConnection 可以加载相应的资源。
比如 上面的例子中用到这个协议 jar:file:/xx.jar ,那么加载资源时,它就会使用这个 jdk jar 包下的 handler 来进行处理
关于协议的扩展可以看这里
https://blog.csdn.net/xiaomin1991222/article/details/50980754
而Springboot 也是重写了这个 handler 来处理嵌套的 jar 资源。
源码
核心代码便是这个 JarFile 以及下面要说到的Launcher 了。
根据 MANIFEST.MF 中指定的 main-class,我们可以发现如下代码
launch 方法如下
第一步,注册协议,registerUrlProtocolHandler
这里就涉及到这个 URLStreamHandler 机制协议了
我们可以通过 JVM 启动参数 -D java.protocol.handler.pkgs 来设置 URLStreamHandler 实现类的包路径
这里的代码也是通过这个系统参数将 URLStreamHandler 实现类的包路径 设置为 loader 包下的。
第二步是 创建 classLoader 。
这里可以看到这些 url 的格式如下
第三步是 判断是否有 jarmode 参数 。这个是和 docker 镜像相关的。
官方文档
https://docs.spring.io/spring-boot/docs/2.4.13/reference/html/spring-boot-features.html#boot-features-container-images
用来简化提取 layer 的操作。
比如以前要写很多 copy,而使用 jarmode 就会自动去 layers.idx 中提取了。(下期再写~)
由于我们没有添加该参数,所以这里是执行 getMainClass() 方法,来获取到这个启动类的。
第四步,运行 run 方法。
这里就创建 MainMethodRunner 类,并执行其中的 run 方法,去反射运行这个 main 方法了
小结一下
除了 JarLauncher 外,源码中的 WarLauncher 和 PropertiesLauncher 也拥有 main 方法,小伙伴们可以自己看看~
JarLauncher和 WarLauncher 都是继承这个ExecutableArchiveLauncher 的。
大坑
到了这里,我陷入了沉思
想到了之前写这个 AOP 插件( 《AOP 插件就这?上手不用两分钟!!》)时,好像也遇到了一点小困难和 jar 包相关的。再次翻看后,我发现我那会写了这么一个注释在 pom 文件中,去掉后打包确实报了这么一个错误。
"用 Springboot 插件打包,但是我们没有 main 方法,会报错,这里跳过就好了"
随后我又在之前的文章中看到了这么一段记录,这里也提到了 jar 包结构的变化 BOOT-INF/classes/ ,还有引出一个问题 ——“URLClassLoader 无法正确加载类,一直出现 ClassNotFoundException ”
到这里,我已经无限好奇了。然后就不自觉地掉进这个牛角尖里了……
为啥 URLClassLoader 就无法加载到这些类了呢?
我尝试用 URLClassLoader 去读取 Springboot 插件打包出来的 jar 包,发现 BOOT-INF/classes/ 下面的类一直无法读取到。但是这个 org 文件夹中的就可以正常加载。
同时我也发现一点不对劲! 没错,在最开始的证明这里,明明可以加载到 BOOT-INF/classes/ 下面的类呀!
于是我做了 n 遍验证,发现在插件篇中,这个 BOOT-INF/classes/ 下面的类一直无法读取到,会一直报错。这我就很纳闷了,直到我发现现在做 demo 的这个项目里,在 pom 文件中就引入了这个 jar 包!我的天~ 去掉之后它也一直报错了,坑死自己了
所以之前在 《AOP 插件就这?上手不用两分钟!!》 一文中提到的结论是没错的。而这也更加验证了 Java 中并没有能加载嵌套 Jar 的方式 ,所以 Springboot 才重写了它的。
而重写后,资源的路径文件夹路径变成下面这种方式了!!
上面这个图是 直到,我发现在 IDEA 中可以直接 debug jar 包 才得来的…… 不然现在还在那里卡着呢
总结
本期思维导图可以在这里获取
https://www.processon.com/embed/62089eed0e3e7407d1cd9ee7
那么 Springboot jar 包为啥可以运行呢?
答:
执行 java -jar xx.jar 命令时,会去 解析MATE-INF 文件夹中的 MANIFEST.MF 清单文件,然后找到 Main-class ,反射运行其中的 main 方法。这个是最根本的原因。
而 Springboot maven 插件打包后的 jar 包结构有所变动,新增 org loader 代码目录和 BOOT-INF 目录,META-INF 目录不变,但是其中的 MANIFEST.MF 发生改变,其中新增 Start-Class 表示真正的启动类,而原本的 Main-Class 则指向JarLauncher , JarLauncher 启动时会去 注册协议,创建 ClassLoader,加载并反射运行 Start-Class 中的 main 方法,来启动程序。重写 Jar 协议是在 Spring boot loader源码中的 JarFile 中进行的,同时重新实现 URLStreamHandler 来解决 嵌套Jar 的问题。
原文链接:https://developer.51cto.com/article/701275.html?utm_source=tuicool&utm_medium=referral
相关推荐
- 《每日电讯报》研发数字工具,教你更有效率地报道新闻
-
为鼓励新闻编辑部持续创新,《每日电讯报》正在尝试有战略地研发数字工具。网站的数字媒体主任马尔科姆o科尔斯(MalcolmColes)表示,《每日电讯报》正试图去“创建一些可持续资产”,以便于让记者们...
- html5学得好不好,看掌握多少标签
-
html5你了解了多少?如果你还是入门阶段的话,或者还是一知半解的话,那么我们专门为你们收集的html5常用的标签大全对你就很有帮助了,你需要了解了html5有哪些标签你才能够更好的。驾驭html5...
- 前端分享-少年了解过iframe么(我想了解少年)
-
iframe就像是HTML的「内嵌画布」,允许在页面中加载独立网页,如同在画布上叠加另一幅动态画卷。核心特性包括:独立上下文:每个iframe都拥有独立的DOM/CSS/JS环境(类似浏...
- 做SEO要知道什么是AJAX(人能看到但搜索引擎看不到的内容)
-
一个明显的,人能看到但搜索引擎不能看到的内容是AJAX。那么什么是AJAX呢?其实,了解过的基本上也都清楚,AJAX不是新的编程语言,而是一种使用现有标准的新方法。AJAX最大的优点是在不重新加...
- 介绍最前沿的人工智能创新,‘无反向传播’神经网络训练方法?
-
图像由GoogleImageFX生成前言:本文整理自NoProp原始论文与实践代码,并结合多个公开实现细节进行了全流程复现。对神经网络训练机制的探索仍在不断演进,如果你也在研究反向传播之...
- 说说我们对HTML6的期许(对html的看法)
-
HTML5概述HTML5是HTML语言最受欢迎的版本之一,它支持音频和视频、离线存储、移动端、和标签属性等等。还提供了article,section,header这样的标签来帮助开发者更好...
- 浏览器中在线预览pdf文件,pdf.mjs插件实现web预览pdf
-
背景:本来只是淘宝上卖卖袜子,想着扩展一下业务,准备做同名“来家居”海外袜子馆外贸项目,碰到pdf在线预览的需求,就找了pdf.js插件进行实践后把此方法记录下来,可以通过多种方法来实现,每种方法都有...
- SVG 在前端的7种使用方法,你还知道哪几种?
-
本文简介点赞+关注+收藏=学会了技术一直在演变,在网页中使用SVG的方法也层出不穷。每个时期都有对应的最优解。所以我打算把我知道的7种SVG的使用方法列举出来,有备无患~如果你还...
- HTML5常用标签大全(html5em标签)
-
HTML前端开发最终取决于掌握标签的多少HTML大概有七八百个标签楼主这里给大家总结了下HTML常用标签标签描述<!--...-->定义注释。<!DOCTYPE>定义文档类型...
- "伪君子Snoop Dogg!"... WHAT?| MetroDaily 24/7
-
TUE.01-新作品-虽说年纪大了会有点糊涂,但是最近SnoopDogg的这波操作实在是让粉丝们有点迷,甚至有人表示没想到他是这样的"伪君子"......而这一切都源于他近日在IG上Po出的一...
- 莎夏·班克斯盼望表哥Snoop Dogg为其作出场曲
-
NXT女子冠军莎夏·班克斯(SashaBanks)近日接受了迈阿密先驱报采访,访谈纪要如下:关于她出众的形象:“我一向喜欢与众不同。为了能让人眼前一亮,我的装束总是非常前卫、非常抢眼,这样才能让观众...
- 喜欢Snoop!全球第一间「史努比博物馆」海外分馆在东京!
-
1950年起,由美國漫畫家CharlesM.Schulz創作的作品《Snoopy》史努比,其鮮明的可愛角色與幽默的劇情內容,至今仍成為許多大朋友與小朋友心中的最愛。為了紀念作者所設立的全球首...
- Vetements 推出 Snoop Dogg 肖像「天价」T-Shirt
-
Vetements的CEOGuramGvasalia早前才透露品牌经营策略的秘密–Vetements如何成为人人热议的话题品牌。但似乎他仍有更多需要解释的东西–这个法国奢侈品牌最新...
- 狗爷Snoop Dogg的《I Wanna Thank Me》巡回演唱会旧金山站
-
西海岸匪帮说唱歌手SnoopDogg在《IWannaThankMe》巡回演唱会旧金山站表演(图片来自ICphoto)西海岸匪帮说唱歌手SnoopDogg(图片来自ICphoto)西海...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)