偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

為什么SpringBoot可以直接運(yùn)行 Jar 包?

開(kāi)發(fā) 架構(gòu)
JAR 文件格式以流行的 ZIP 文件格式為基礎(chǔ)。與 ZIP 文件不同的是,JAR 文件不僅用于壓縮和發(fā)布,而且還用于部署和封裝庫(kù)、組件和插件程序,并可被像編譯器和 JVM 這樣的工具直接使用。在 JAR 中包含特殊的文件,如 manifests 和部署描述符,用來(lái)指示工具如何處理特定的 JAR。

小伙伴們好呀,今天咋們來(lái)探索下,為什么 SpringBoot 的 jar 包可以直接運(yùn)行? 以及 4ye 踩到的坑????

目錄如下 ??

開(kāi)始之前,先簡(jiǎn)單介紹下這個(gè) jar ??

什么是 jar

JAR 文件格式以流行的 ZIP 文件格式為基礎(chǔ)。與 ZIP 文件不同的是,JAR 文件不僅用于壓縮和發(fā)布,而且還用于部署和封裝庫(kù)、組件和插件程序,并可被像編譯器和 JVM 這樣的工具直接使用。在 JAR 中包含特殊的文件,如 manifests 和部署描述符,用來(lái)指示工具如何處理特定的 JAR?!栋俣劝倏啤?/p>

jar包結(jié)構(gòu)圖

這里小伙伴們可以自行查找下 jar文件規(guī)范 ??

例如 https://blog.51cto.com/robinc/547658

規(guī)范中最重要的一點(diǎn),就是 MATE-INF 文件夾中的 MANIFEST.MF 清單文件了。

文件內(nèi)容如下

一眼看過(guò)去,這個(gè) Main-Class 配置就特別突出了。?? 它指明了這個(gè)啟動(dòng)類的位置。

當(dāng)我們用 java -jar xx.jar 命令運(yùn)行一個(gè) jar 包時(shí),無(wú)外乎,它肯定是幫我們找到這個(gè) main 方法,然后啟動(dòng)它。

(ps:使用 -jar 時(shí),會(huì)忽略 classpath 環(huán)境的配置。)

這里我將上期 Map 專題的代碼進(jìn)行打包,運(yùn)行效果如下

可以看到第一次運(yùn)行時(shí)出現(xiàn)沒(méi)有主清單屬性的提示 。

在 pom 文件中添加 Main-class 配置(如下??),即可解決問(wèn)題。

<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>

那么,到這里,基本的秘密已經(jīng)被我們知道了。

執(zhí)行 java -jar xx.jar 命令時(shí),會(huì)去:

解析 MATE-INF 文件夾中的MANIFEST.MF 清單文件,

然后找到 Main-class ,運(yùn)行其中的 main方法。

接著我們?cè)俜催^(guò)來(lái)看看這個(gè) Springboot 的 jar 包有啥不同。

官方文檔參考

建議大家先去讀讀這個(gè)文檔~??

地址:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#executable-jar

沒(méi)看文檔前,我想得很簡(jiǎn)單,直接就打開(kāi) Springboot 打包好的 jar 包去找 META-INF 文件夾下的 MANIFEST.MF

看到后,我的理解如下圖

其他配置應(yīng)該是表明這個(gè) classes ,lib 去哪里找。

實(shí)際上呢,這個(gè)理解也沒(méi)有錯(cuò),但是里面多了很多細(xì)節(jié)~

比如:

  • 嵌套的 jar 包要怎么解決
  • classpath.idx 和 layers.idx 是用來(lái)干嘛的
  • jarmode 又是什么

下面就讓我們來(lái)了解探索下它的奧秘叭。(也順便看看 4ye 掉到怎樣的牛角尖去了叭??)

META-INF

我們可以看到,Springboot 插件打包后生成的 jar 包和原來(lái)是有很大的不同的。

除了 META-INF 文件夾結(jié)構(gòu)沒(méi)變化之外。

BOOT-INF

結(jié)構(gòu)如下??

這里的重點(diǎn)在 classpath.idx 和 layers.idx 這兩個(gè)索引文件。

classpath.idx 可以被 jar 和 war 包使用,它配置了哪些 jar 包要被加載到 classpath 中。

layers.idx 只能被 jar 包使用,在 創(chuàng)建鏡像 的時(shí)候被使用,如 Docker/OCI (OCI是一種容器標(biāo)準(zhǔn))

原話如下:

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.

官方文檔中詳細(xì)說(shuō)明了這個(gè) layers.idx 的規(guī)范。

比如 "dependencies" 是這部分 layer 的名稱,下面的都屬于這個(gè) layer。

org

這里就是 Springboot 運(yùn)行 jar 包的秘密了。

org文件夾

這里先介紹下一些背景,然后再簡(jiǎn)單看看源碼~??

背景一

Fat Jar 指的就是這種 jar in jar , 或者說(shuō)嵌套的 Jar 包。但是 Java 中并沒(méi)有能加載嵌套 Jar 的方式,所以 Spring boot 自己寫了這套代碼,來(lái)解決這個(gè)問(wèn)題。

當(dāng)然,這句話是從 Springboot 官方文檔中發(fā)現(xiàn)的

Java does not provide any standard way to load nested jar files (that is, jar files that are themselves contained within a jar).

到了這里,我就掉入一個(gè)坑了。。?? 因?yàn)閷?duì)這塊不熟悉,我一直以為它說(shuō)的就是 URLClassLoader 無(wú)法加載到這里面的class,可是我測(cè)試了好多遍,發(fā)現(xiàn)嵌套在里面的 class 可以被加載到呀??(自己挖的坑??,文末解答)

我翻了很多資料,還去查看 GitHub 的 issue,發(fā)現(xiàn)都沒(méi)有人提過(guò)相關(guān)的問(wèn)題??

到了這里,我已經(jīng)非常非常無(wú)奈了!這痛苦的感覺(jué),就像剛開(kāi)始學(xué)習(xí)編程時(shí),裝環(huán)境被各種奇奇怪怪的 bug 搞到漸漸沒(méi)脾氣。

被折磨了 N 久之后,我又找了另外一個(gè)角度,難道它說(shuō)的是這個(gè) JarFile 。我的天,我嘗試了一下后,發(fā)現(xiàn)確實(shí)沒(méi)有辦法加載嵌套的 Jar

證明如下 ??

背景二

URLStreamHandler ,它是用來(lái)處理各種協(xié)議的(比如 http,file,jar 等等),配合 URL,URLConnection 可以加載相應(yīng)的資源。

比如 上面的例子中用到這個(gè)協(xié)議 jar:file:/xx.jar ,那么加載資源時(shí),它就會(huì)使用這個(gè) jdk jar 包下的 handler 來(lái)進(jìn)行處理

關(guān)于協(xié)議的擴(kuò)展可以看這里

https://blog.csdn.net/xiaomin1991222/article/details/50980754

而Springboot 也是重寫了這個(gè) handler 來(lái)處理嵌套的 jar 資源。

源碼

核心代碼便是這個(gè) JarFile 以及下面要說(shuō)到的Launcher 了。??

根據(jù) MANIFEST.MF 中指定的 main-class,我們可以發(fā)現(xiàn)如下代碼

launch 方法如下

第一步,注冊(cè)協(xié)議,registerUrlProtocolHandler

這里就涉及到這個(gè) URLStreamHandler 機(jī)制協(xié)議了

我們可以通過(guò) JVM 啟動(dòng)參數(shù) -D java.protocol.handler.pkgs 來(lái)設(shè)置 URLStreamHandler 實(shí)現(xiàn)類的包路徑

這里的代碼也是通過(guò)這個(gè)系統(tǒng)參數(shù)將 URLStreamHandler 實(shí)現(xiàn)類的包路徑 設(shè)置為 loader 包下的。

第二步是 創(chuàng)建 classLoader 。

這里可以看到這些 url 的格式如下

第三步是 判斷是否有 jarmode 參數(shù) 。這個(gè)是和 docker 鏡像相關(guān)的。

官方文檔 ??

https://docs.spring.io/spring-boot/docs/2.4.13/reference/html/spring-boot-features.html#boot-features-container-images

用來(lái)簡(jiǎn)化提取 layer 的操作。

比如以前要寫很多 copy,而使用 jarmode 就會(huì)自動(dòng)去 layers.idx 中提取了。(下期再寫~)

由于我們沒(méi)有添加該參數(shù),所以這里是執(zhí)行 getMainClass() 方法,來(lái)獲取到這個(gè)啟動(dòng)類的。

第四步,運(yùn)行 run 方法。

這里就創(chuàng)建 MainMethodRunner 類,并執(zhí)行其中的 run 方法,去反射運(yùn)行這個(gè) main 方法了??

小結(jié)一下

除了 JarLauncher 外,源碼中的 WarLauncher 和 PropertiesLauncher 也擁有 main 方法,小伙伴們可以自己看看~

JarLauncher和 WarLauncher 都是繼承這個(gè)ExecutableArchiveLauncher 的。

大坑??

到了這里,我陷入了沉思 ??

想到了之前寫這個(gè) AOP 插件( 《AOP 插件就這?上手不用兩分鐘!!》)時(shí),好像也遇到了一點(diǎn)小困難和 jar 包相關(guān)的。再次翻看后,我發(fā)現(xiàn)我那會(huì)寫了這么一個(gè)注釋在 pom 文件中,去掉后打包確實(shí)報(bào)了這么一個(gè)錯(cuò)誤。

"用 Springboot 插件打包,但是我們沒(méi)有 main 方法,會(huì)報(bào)錯(cuò),這里跳過(guò)就好了"

隨后我又在之前的文章中看到了這么一段記錄,這里也提到了 jar 包結(jié)構(gòu)的變化 BOOT-INF/classes/ ,還有引出一個(gè)問(wèn)題 ——“URLClassLoader 無(wú)法正確加載類,一直出現(xiàn) ClassNotFoundException ”

到這里,我已經(jīng)無(wú)限好奇了。然后就不自覺(jué)地掉進(jìn)這個(gè)牛角尖里了……

為啥 URLClassLoader 就無(wú)法加載到這些類了呢? ??

我嘗試用 URLClassLoader 去讀取 Springboot 插件打包出來(lái)的 jar 包,發(fā)現(xiàn) BOOT-INF/classes/ 下面的類一直無(wú)法讀取到。但是這個(gè) org 文件夾中的就可以正常加載。

同時(shí)我也發(fā)現(xiàn)一點(diǎn)不對(duì)勁! 沒(méi)錯(cuò),在最開(kāi)始的證明這里,明明可以加載到 BOOT-INF/classes/ 下面的類呀!

于是我做了 n 遍驗(yàn)證,發(fā)現(xiàn)在插件篇中,這個(gè) BOOT-INF/classes/ 下面的類一直無(wú)法讀取到,會(huì)一直報(bào)錯(cuò)。這我就很納悶了,直到我發(fā)現(xiàn)現(xiàn)在做 demo 的這個(gè)項(xiàng)目里,在 pom 文件中就引入了這個(gè) jar 包!我的天~ 去掉之后它也一直報(bào)錯(cuò)了,坑死自己了??

所以之前在 《AOP 插件就這?上手不用兩分鐘!!》 一文中提到的結(jié)論是沒(méi)錯(cuò)的。而這也更加驗(yàn)證了 Java 中并沒(méi)有能加載嵌套 Jar 的方式 ,所以 Springboot 才重寫了它的。

而重寫后,資源的路徑文件夾路徑變成下面這種方式了!!

上面這個(gè)圖是 直到,我發(fā)現(xiàn)在 IDEA 中可以直接 debug jar 包 ?? 才得來(lái)的…… 不然現(xiàn)在還在那里卡著呢??

總結(jié)

本期思維導(dǎo)圖可以在這里獲取

https://www.processon.com/embed/62089eed0e3e7407d1cd9ee7

那么 Springboot jar 包為啥可以運(yùn)行呢?

答:

執(zhí)行 java -jar xx.jar 命令時(shí),會(huì)去 解析MATE-INF 文件夾中的 MANIFEST.MF 清單文件,然后找到 Main-class ,反射運(yùn)行其中的 main 方法。這個(gè)是最根本的原因。

而 Springboot maven 插件打包后的 jar 包結(jié)構(gòu)有所變動(dòng),新增 org loader 代碼目錄和 BOOT-INF 目錄,META-INF 目錄不變,但是其中的 MANIFEST.MF 發(fā)生改變,其中新增 Start-Class 表示真正的啟動(dòng)類,而原本的 Main-Class 則指向JarLauncher , JarLauncher 啟動(dòng)時(shí)會(huì)去 注冊(cè)協(xié)議,創(chuàng)建 ClassLoader,加載并反射運(yùn)行 Start-Class 中的 main 方法,來(lái)啟動(dòng)程序。重寫 Jar 協(xié)議是在 Spring boot loader源碼中的 JarFile 中進(jìn)行的,同時(shí)重新實(shí)現(xiàn) URLStreamHandler 來(lái)解決 嵌套Jar 的問(wèn)題。


責(zé)任編輯:武曉燕 來(lái)源: Java4ye
相關(guān)推薦

2024-04-03 09:01:34

SpringTomcat容器

2023-11-30 08:16:19

SpringjarTomcat

2020-05-07 16:30:32

Spring BootJava

2024-11-26 08:36:56

SpringJar機(jī)制

2015-08-17 10:16:00

CentOSDocker命令root

2020-03-19 08:59:15

SpringMVC啟動(dòng)過(guò)程

2020-08-24 15:56:49

AndroidWindows 10三星

2023-04-04 22:23:09

2009-06-29 18:35:41

操作系統(tǒng)服務(wù)器軟件

2019-03-06 13:45:20

Windows 10Xbox微軟

2023-09-01 08:26:06

SpringBootjar包war包

2020-08-27 11:35:36

Python 開(kāi)發(fā)編程語(yǔ)言

2021-04-16 17:02:21

數(shù)組C++語(yǔ)言

2020-11-11 16:46:35

蘋果macOS操作系統(tǒng)

2024-09-13 08:57:25

SpringJar項(xiàng)目

2024-09-14 07:00:28

SpringBoot代碼反編譯

2020-10-22 14:20:39

Parallels

2024-05-29 10:43:31

2015-05-12 10:34:45

2023-12-20 07:36:58

GoLinux語(yǔ)言
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)