Spring Boot 的java -jar命令啟動(dòng)原理詳解
導(dǎo)語(yǔ)
在運(yùn)用Spring Boot 后,我們基本上擺脫之前項(xiàng)目每次上線的時(shí)候把項(xiàng)目打成war包。當(dāng)然也不排除一些奇葩的規(guī)定,必須要用war包上線,不過(guò)很多時(shí)候,我們對(duì)一些東西只是處在使用的階段,并不會(huì)去深入的研究使用的原理是什么,這貌似也是大多數(shù)人的固定思維。
或許正是如此,總會(huì)有些沒(méi)有固定思維的人會(huì)去積極的探索原理,當(dāng)然這話不是說(shuō)我是積極的,我其實(shí)也是只原理的搬運(yùn)工。今天和大家來(lái)簡(jiǎn)單的說(shuō)下Spring Boot 的項(xiàng)目在運(yùn)行Java -jar的原理。
jar包目錄和jar命令啟動(dòng)入口
在正式開(kāi)始之前,我們先來(lái)看看把jar包進(jìn)行解壓。然后用tree /f命令查看目錄結(jié)構(gòu)(由于筆者寫(xiě)博文時(shí)用的是window,所以用的是tree /f命令),由于目錄結(jié)構(gòu)太長(zhǎng),這里做了相應(yīng)省略,如下:
├─BOOT-INF
│ ├─classes
│ │ │ application.properties
│ │ │
│ │ └─com
│ │ └─spring
│ │ └─boot
│ │ └─test
│ │ SpringBootTestApplication.class
│ │
│ └─lib
│ classmate-1.5.1.jar
│ hibernate-validator-6.0.18.Final.jar
│ …………此處省略…………
│
├─META-INF
│ │ MANIFEST.MF
│ │
│ └─maven
│ └─com.spring.boot.test
│ └─spring-boot-test
│ pom.properties
│ pom.xml
│
└─org
└─springframework
└─boot
└─loader
│ ExecutableArchiveLauncher.class
│ JarLauncher.class
│ LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
│ LaunchedURLClassLoader.class
│ Launcher.class
│ MainMethodRunner.class
│ PropertiesLauncher$1.class
│ PropertiesLauncher$ArchiveEntryFilter.class
│ PropertiesLauncher$PrefixMatchingArchiveFilter.class
│ PropertiesLauncher.class
│ WarLauncher.class
│
├─archive
│ Archive$Entry.class
│ …………此處省略…………
│
├─data
│ RandomAccessData.class
│ …………此處省略…………
│
├─jar
│ AsciiBytes.class
│ Bytes.class
│ …………此處省略…………
│
└─util
SystemPropertyUtils.class
先簡(jiǎn)單說(shuō)下上面目錄結(jié)構(gòu),大體目錄分三層:BOOT-INF、META-INF、org,BOOT-INF是存放對(duì)應(yīng)的應(yīng)用服務(wù)的.class文件和Maven依賴(lài)的jar包,包括啟動(dòng)類(lèi)SpringBootTestApplication,META-INF下存放的是Maven相關(guān)的pom信息和MANIFEST.MF文件,org文件夾下存放的是Spring boot loader模塊編譯的.class文件,也就是jar啟動(dòng)的關(guān)鍵代碼所在。
在執(zhí)行java -jar命令的時(shí)候,它的啟動(dòng)類(lèi)配置實(shí)在jar包目錄下META-INF文件夾下的名MANIFEST.MF文件中,在這個(gè)文件中有一個(gè)名為Main-Class的屬性,我們來(lái)看下這個(gè)文件的具體內(nèi)容:
Manifest-Version: 1.0 Implementation-Title: spring-boot-test Implementation-Version: 0.0.1-SNAPSHOT Start-Class: com.spring.boot.test.SpringBootTestApplication Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Build-Jdk-Spec: 1.8 Spring-Boot-Version: 2.2.3.RELEASE Created-By: Maven Archiver 3.4.0 Main-Class: org.springframework.boot.loader.JarLauncher
從上面的配置文件中,可以看到Main-Class屬性指向的Class為org.springframework.boot.loader.JarLauncher,而JarLauncher是JAR的啟動(dòng)器,這個(gè)類(lèi)是在org/springframework/boot/loader/,然后可以看到項(xiàng)目所定義的啟動(dòng)類(lèi)是指向Start-Class這個(gè)屬性的。
JAR文件啟動(dòng)器——JarLauncher
在上面我們說(shuō)了JarLauncher是JAR可執(zhí)行的啟動(dòng)器,那么它和項(xiàng)目的啟動(dòng)類(lèi)SpringBootTestApplication有什么關(guān)聯(lián)呢?先給大家來(lái)個(gè)示例,先來(lái)到解壓目錄下執(zhí)行命令:java org.springframework.boot.loader.JarLauncher ,然后便是如下界面:
C:\Users\elisha\Desktop\spring-boot-test-0.0.1-SNAPSHOT>java org.springframework.boot.loader.JarLauncher . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.2.3.RELEASE) 2020-01-18 14:28:19.866 INFO 3644 --- [ main] c.s.boot.test.SpringBootTestApplication : Starting SpringBootTestApplication on LAPTOP-R2NNI9CM with PID 3644 (C:\Users\elisha\Desktop\spring-boot-test-0.0.1-SNAPSHOT\BOOT-INF\classes started by elisha in C:\Users\elisha\Desktop\spring-boot-test-0.0.1-SNAPSHOT)
從上面的執(zhí)行接口可以看到項(xiàng)目引導(dǎo)類(lèi)SpringBootTestApplication會(huì)被JarLauncher類(lèi)進(jìn)行引導(dǎo),但是如果我們到BOOT-INF/class目錄下,然后也執(zhí)行java com.spring.boot.test.SpringBootTestApplication,會(huì)報(bào)SpringApplication的ClassNotFoundException這個(gè)錯(cuò)誤,由此可以得知這是因?yàn)閖ava命令未指定Class Path。不過(guò)當(dāng)前Spring Boot依賴(lài)的JAR文件都是存放在BOOT-INF/lib下,而org.springframework.boot.loader.JarLauncher會(huì)將JAR作為SpringBootTestApplication類(lèi)庫(kù)的依賴(lài),這也就是為什么JarLauncher能引導(dǎo)SpringBootTestApplication,反之則是不可以的,那么對(duì)于SpringBootTestApplication是JarLauncher的子進(jìn)程,還是處于同一層級(jí)呢?接下來(lái)我們來(lái)看看JarLauncher的原理。
JarLauncher實(shí)現(xiàn)引導(dǎo)原理
因?yàn)閛rg.springframework.boot.loader.JarLauncher的類(lèi)是在spring-boot-loader中,但是若想在IDEA中來(lái)看源碼,需要在pom文件中引入如下配置:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-loader</artifactId> <scope>provided</scope> </dependency>
在引入上面的配置文件后,便可以在IDEA中查看源碼了,使用CTRL+N命令來(lái)搜索JarLauncher類(lèi),那就來(lái)看下源碼,如下:
public class JarLauncher extends ExecutableArchiveLauncher {
static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
static final String BOOT_INF_LIB = "BOOT-INF/lib/";
public JarLauncher() {
}
protected JarLauncher(Archive archive) {
super(archive);
}
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
}
從上面的JarLauncher類(lèi)中,可以看到兩個(gè)常量:BOOT_INF_CLASSES、BOOT_INF_LIB,而它們又分別指向如下路徑:BOOT-INF/classes/、BOOT-INF/lib/,并用isNestedArchive(Archive.Entry entry)方法進(jìn)行判斷(在Spring Boot中Archive,抽象出了Archive的概念,一個(gè)Archive可以是一個(gè)Jar(JarFileArchive)、也可以是一個(gè)目錄(ExplodedArchive),在這里可以理解為Spring Boot抽象出來(lái)的同一訪問(wèn)資源層。),從isNestedArchive方法的參數(shù)Archive.Entry對(duì)象貌似為一個(gè)JAR文件中的資源,譬如application.properties,同時(shí)這個(gè)對(duì)象和JarEntry是類(lèi)似的,其name屬性(Archive.Entry#getName())便是Jar資源的相對(duì)路徑。當(dāng)application.properties資源在FAT JAR目錄下時(shí),其實(shí)Archive.Entry#getName()就是/BOOT-INF/classes/application.properties,此時(shí)便符合startsWith方法的判斷,所以isNestedArchive(Archive.Entry entry)便返回為true。當(dāng)返回為false時(shí),便說(shuō)明FAT JAR被解壓到文件目錄了,由此也說(shuō)明了Spring Boot應(yīng)用可以通過(guò)java org.springframework.boot.loader.JarLauncher 命令啟動(dòng)的原因了。
Archive.Entry的實(shí)現(xiàn)
上面說(shuō)了在Spring Boot中Archive,抽象出了Archive的概念,一個(gè)Archive可以是一個(gè)Jar(JarFileArchive)、也可以是一個(gè)目錄(ExplodedArchive),這里所說(shuō)的JarFileArchive、ExplodedArchive便是Archive的兩種是想方式,對(duì)于這兩個(gè)類(lèi)的實(shí)現(xiàn)代碼感興趣額同學(xué)可以自己去看看。
不過(guò)由此也說(shuō)明了JarLauncher 既支持JAR啟動(dòng),又支持文件系統(tǒng)啟動(dòng)。同時(shí)JarLauncher 在作為引導(dǎo)類(lèi)的時(shí)候,當(dāng)執(zhí)行java -jar 命令式,/META-INF/ 下MANIFEST.MF文件中的Main-Class屬性將調(diào)用它的,main(String [])方法,其實(shí)它還是調(diào)用JarLauncher #launch(args)方法,這個(gè)方法是實(shí)現(xiàn)基類(lèi)Launcher,這里簡(jiǎn)單看下這個(gè)方法的實(shí)現(xiàn):
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
ClassLoader classLoader = createClassLoader(getClassPathArchives());
launch(args, getMainClass(), classLoader);
}
總結(jié)
本篇文章簡(jiǎn)單的講解了一下,java -jar命令的一個(gè)執(zhí)行的原理,首先說(shuō)了下jar包目錄和jar命令啟動(dòng)入口,然后說(shuō)了下JAR文件啟動(dòng)器——JarLauncher和JarLauncher實(shí)現(xiàn)引導(dǎo)原理,最后說(shuō)了下Archive.Entry的實(shí)現(xiàn),這個(gè)實(shí)現(xiàn)的原理也是比較復(fù)雜,后面如果有機(jī)會(huì),會(huì)再寫(xiě)篇文章來(lái)進(jìn)行說(shuō)明。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解SpringBoot Redis自適應(yīng)配置(Cluster Standalone Sentinel)
這篇文章主要介紹了詳解SpringBoot Redis自適應(yīng)配置(Cluster Standalone Sentinel),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Gradle的SpringBoot項(xiàng)目構(gòu)建圖解
這篇文章主要介紹了Gradle的SpringBoot項(xiàng)目構(gòu)建圖解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
關(guān)于?Math.random()生成指定范圍內(nèi)的隨機(jī)數(shù)的公式推導(dǎo)問(wèn)題
在 java 中,用于生成隨機(jī)數(shù)的 Math 方法 random()只能生成 0-1 之間的隨機(jī)數(shù),而對(duì)于生成指定區(qū)間,例如 a-b 之間的隨機(jī)數(shù),卻只能用相關(guān)計(jì)算公式,今天通過(guò)本文給大家介紹Math.random()生成隨機(jī)數(shù)的公式推導(dǎo)問(wèn)題,感興趣的朋友一起看看吧2022-09-09

