SpringBoot的jar包如何啟動(dòng)的實(shí)現(xiàn)
一、簡(jiǎn)介
? 使用過(guò)SprongBoot打過(guò)jar包的都應(yīng)該知道,目標(biāo)文件一般都會(huì)生成兩個(gè)文件,一個(gè)是以.jar的包,一個(gè)是.jar.original文件。那么使用SpringBoot會(huì)打出兩個(gè)包,而.jar.original的作用是什么呢?還有就是java -jar是如何將一個(gè)SpringBoot項(xiàng)目啟動(dòng),之間都進(jìn)行了那些的操作?
? 其實(shí).jar.original是maven在SpringBoot重新打包之前的原始jar包,內(nèi)部只包含了項(xiàng)目的用戶類,不包含其他的依賴jar包,生成之后,SpringBoot重新打包之后,最后生成.jar包,內(nèi)部包含了原始jar包以及其他的引用依賴。以下提及的jar包都是SpringBoot二次加工打的包。
二、jar包的內(nèi)部結(jié)構(gòu)
SpringBoot打出的jar包,可以直接通過(guò)解壓的方式查看內(nèi)部的構(gòu)造。一般情況下有三個(gè)目錄。
BOOT-INF:這個(gè)文件夾下有兩個(gè)文件夾classes用來(lái)存放用戶類,也就是原始jar.original里的類;還有一個(gè)是lib,就是這個(gè)原始jar.original引用的依賴。META-INF:這里是通過(guò)java -jar啟動(dòng)的入口信息,記錄了入口類的位置等信息。org:Springboot loader的代碼,通過(guò)它來(lái)啟動(dòng)。
這里主要介紹一下/BOOT-INF/MANIFEST.MF文件
Main-Class: org.springframework.boot.loader.JarLauncher Start-Class: cn.com.springboot.center.AuthEenterBootstrap
Main-Class:記錄了java -jar的啟動(dòng)入口,當(dāng)使用該命令啟動(dòng)時(shí)就會(huì)調(diào)用這個(gè)入口類的main方法,顯然可以看出,Springboot轉(zhuǎn)移了啟動(dòng)的入口,不是用戶編寫的xxx.xxx.BootStrap的那個(gè)入口類。
Start-Class:記錄了用戶編寫的xxx.xxx.BootStrap的那個(gè)入口類,當(dāng)內(nèi)嵌的jar包加載完成之后,會(huì)使用LaunchedURLClassLoader線程加載類來(lái)加載這個(gè)用戶編寫的入口類。
三、加載過(guò)程
1.使用到的一些類
3.1.1 Archive
? 歸檔文件接口,實(shí)現(xiàn)迭代器接口,它有兩個(gè)子類,一個(gè)是JarFileArchive對(duì)jar包文件使用,提供了返回這個(gè)jar文件對(duì)應(yīng)的url、或者這個(gè)jar文件的MANIFEST文件數(shù)據(jù)信息等操作。是ExplodedArchive是文件目錄的使用也有獲取這個(gè)目錄url的方法,以及獲取這個(gè)目錄下的所有Archive文件方法。

3.1.2 Launcher
? 啟動(dòng)程序的基類,這邊最后是通過(guò)JarLauncher#main()來(lái)啟動(dòng)。ExecutableArchiveLauncher是抽象類,提供了獲取Start-Class類路徑的方法,以及是否還有內(nèi)嵌對(duì)應(yīng)文件的判斷方法和獲取到內(nèi)嵌對(duì)應(yīng)文件集合的后置處理方法的抽象,由子類JarLauncher和WarLauncher自行實(shí)現(xiàn)。

3.1.3 Spring.loader下的JarFile和JarEntry
? jarFile繼承于jar.util.jar.JarFile,JarEntry繼承于java.util.jar.JarEntry,對(duì)原始的一些方法進(jìn)行重寫覆蓋。每一個(gè)JarFileArchive都擁有一個(gè)JarFile方法,用于存儲(chǔ)這個(gè)jar包對(duì)應(yīng)的文件,而每一個(gè)JarFile都有一個(gè)JarFileEntries,JarFileEntries是一個(gè)迭代器??偟膩?lái)說(shuō),在解析jar包時(shí),會(huì)將jar包內(nèi)的文件封裝成JarEntry對(duì)象后由JarFile對(duì)象保存文件列表的迭代器。所以JarFileArchive和JarFileEntries之間是通過(guò)JarFile連接,二者都可以獲取到JarFile對(duì)象。
2.過(guò)程分析
從MANIFEST.MF文件中的Main-class指向入口開始。
創(chuàng)建JarLauncher并且通過(guò)它的launch()方法開始加載jar包內(nèi)部信息。
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}JarLauncher的空構(gòu)造方法時(shí)一個(gè)空實(shí)現(xiàn),剛開始看的時(shí)候還懵了一下,以為是在后續(xù)的操作中去加載的文件,其實(shí)不然,在創(chuàng)建時(shí)由父類ExecutableArchiveLauncher的構(gòu)造方法去加載的文件。
加載為歸檔文件對(duì)象:
this.archive = createArchive();
具體的加載方法:判斷路徑是否是一個(gè)文件夾,是則返回ExplodedArchive對(duì)象,否則返回JarFileArchive 進(jìn)入JarFileArchive類:通過(guò)這個(gè)new方法創(chuàng)建JarFile對(duì)象
public class JarFileArchive implements Archive {
public JarFileArchive(File file, URL url) throws IOException {
this(new JarFile(file));
this.url = url;
}
}進(jìn)入到JarFile方法:通過(guò)RandomAccessDataFile讀取文件的內(nèi)容,并傳遞給本類中的方法進(jìn)行具體的解析。
public class JarFile extends java.util.jar.JarFile {
public JarFile(File file) throws IOException {
this(new RandomAccessDataFile(file));
}
}進(jìn)入jarLauncher的launch方法:注冊(cè)URL協(xié)議的處理器,沒(méi)有指定時(shí),默認(rèn)指向org.springframework.boot.loader包路徑,獲取類路徑下的歸檔文件Archive并通過(guò)這些歸檔文件的URL,創(chuàng)建線程上下文類加載器,使用類加載器和用戶編寫的啟動(dòng)入口類,通過(guò)反射調(diào)用它的main方法。
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
ClassLoader classLoader = createClassLoader(getClassPathArchives());
launch(args, getMainClass(), classLoader);
}JarLauncher的getClassPathArchives是在ExecutableArchiveLauncher中實(shí)現(xiàn):獲取歸檔文件中滿足EntryFilterg過(guò)濾器的項(xiàng),isNestedArchive方法由具體的之類實(shí)現(xiàn)。獲取到當(dāng)前歸檔文件下的所有子歸檔文件之后的后置操作,是一個(gè)擴(kuò)展點(diǎn)。在JarLauncher中是一個(gè)空實(shí)現(xiàn)。
JarLauncher的具體實(shí)現(xiàn),這里通過(guò)判斷是否在BOOT-INF/lib/包下返回true 也就是說(shuō)只會(huì)把jar包下的BOOT-INF/lib/下的文件加載為Archive對(duì)象
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}JarFileArchive的getNestedArchives方法:若匹配器匹配到則獲取內(nèi)嵌歸檔文件。
具體的獲取內(nèi)嵌歸檔文件邏輯:根據(jù)具體的Entry對(duì)象,創(chuàng)建JarFile對(duì)象并封裝成歸檔文件對(duì)象后返回。
protected Archive getNestedArchive(Entry entry) throws IOException {
try {
JarFile jarFile = this.jarFile.getNestedJarFile(jarEntry);
return new JarFileArchive(jarFile);
}
}獲取到參數(shù)entry對(duì)應(yīng)的RandomAccessData對(duì)象,這里根據(jù)springboot擴(kuò)展的url協(xié)議,在父路徑的基礎(chǔ)上添加!/來(lái)標(biāo)記子包。
private JarFile createJarFileFromFileEntry(JarEntry entry) throws IOException {
RandomAccessData entryData = this.entries.getEntryData(entry.getName());
return new JarFile(this.rootFile, this.pathFromRoot + "!/" + entry.getName(),
entryData, JarFileType.NESTED_JAR);
}到這基本上讀取jar內(nèi)部信息,加載為對(duì)應(yīng)歸檔文件對(duì)象的大概過(guò)程已經(jīng)講完了,接下來(lái)分析一下在獲取到了整個(gè)jar的歸檔文件對(duì)象后的處理。
通過(guò)歸檔文件對(duì)象列表,獲取對(duì)應(yīng)的url信息,并通過(guò)url信息創(chuàng)建LaunchedURLClassLoader
protected ClassLoader createClassLoader(List<Archive> archives) {
List<URL> urls = new ArrayList<URL>(archives.size());
for (Archive archive : archives) {
urls.add(archive.getUrl());
}
return createClassLoader(urls.toArray(new URL[urls.size()]));
}獲取到對(duì)應(yīng)的LaunchedUrlClassLoader類加載器之后,設(shè)置線程的上下文類加載器為該加載器。根據(jù)MANIFI.MF文件中的start-classs信息創(chuàng)建項(xiàng)目啟動(dòng)入口主類對(duì)象,并通過(guò)返回對(duì)象的run方法啟動(dòng)
protected void launch(String[] args, String mainClass, ClassLoader classLoader) {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run();
}進(jìn)入MainMethodRunner的run方法:先通過(guò)當(dāng)前線程獲取到main入口類,然后通過(guò)反射調(diào)用啟動(dòng)項(xiàng)目啟動(dòng)類的main方法
public void run() throws Exception {
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[] { this.args });
}最后來(lái)說(shuō)一下這個(gè)LaunchedURLClassLoader,它繼承于URLClassLoader,并重寫了loadClass方法
LaunchedClassLoader的loadClass方法:調(diào)用父類loadClass方法,走正常委派流程,最終會(huì)被LaunchURLClassLoader加載。
@Override
protected Class<?> loadClass(String name, boolean resolve){
try {
try {
definePackageIfNecessary(name);
}
return super.loadClass(name, resolve);
}
}進(jìn)入URLClassLoader中根據(jù)springboot解析進(jìn)行解析。根據(jù)名稱將路徑轉(zhuǎn)化為以.class結(jié)尾的/分隔的格式。通過(guò)UrlClassPath對(duì)象根據(jù)路徑獲取資源類文件
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
}
}
}
}四、總結(jié)
? Springboot主要實(shí)現(xiàn)了對(duì)URL加載方式進(jìn)行了擴(kuò)展,并且對(duì)一些對(duì)象Archive、JarFile、Entry等進(jìn)行了抽象和擴(kuò)展,最后使用LaunchedUrlClassLoader來(lái)進(jìn)行處理。
到此這篇關(guān)于SpringBoot的jar包如何啟動(dòng)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringBoot jar包啟動(dòng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
idea中無(wú)法自動(dòng)裝配未找到 ‘XXXXXXX‘ 類型的 Bean
本文主要介紹了idea中無(wú)法自動(dòng)裝配未找到 ‘XXXXXXX‘ 類型的 Bean的原因及三種解決方法,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03
Java生產(chǎn)者和消費(fèi)者例子_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
生產(chǎn)者-消費(fèi)者(producer-consumer)問(wèn)題,也稱作有界緩沖區(qū)(bounded-buffer)問(wèn)題,兩個(gè)進(jìn)程共享一個(gè)公共的固定大小的緩沖區(qū)。下文通過(guò)實(shí)例給大家介紹java生產(chǎn)者和消費(fèi)者,感興趣的朋友一起學(xué)習(xí)吧2017-05-05
在SpringBoot中使用@Value注解來(lái)設(shè)置默認(rèn)值的方法
Spring Boot提供了一種使用注解設(shè)置默認(rèn)值的方式,即使用 @Value 注解,下面這篇文章主要給大家介紹了關(guān)于如何在SpringBoot中使用@Value注解來(lái)設(shè)置默認(rèn)值的相關(guān)資料,需要的朋友可以參考下2023-10-10
使用Java實(shí)現(xiàn)通用樹形結(jié)構(gòu)構(gòu)建工具類
這篇文章主要為大家詳細(xì)介紹了如何使用Java實(shí)現(xiàn)通用樹形結(jié)構(gòu)構(gòu)建工具類,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-03-03
Flink結(jié)合Kafka實(shí)現(xiàn)通用流式數(shù)據(jù)處理
這篇文章將和大家一起深入探討Flink和Kafka的關(guān)系以及它們?cè)跀?shù)據(jù)流處理中的應(yīng)用,并提供一些最佳實(shí)踐和實(shí)際案例,希望對(duì)大家有一定的幫助2025-03-03
springboot 自定義異常并捕獲異常返給前端的實(shí)現(xiàn)代碼
在開發(fā)中,如果用try catch的方式,每個(gè)方法都需要單獨(dú)實(shí)現(xiàn),為了方便分類異常,返回給前端,采用了@ControllerAdvice注解和繼承了RuntimeException的方式來(lái)實(shí)現(xiàn),具體實(shí)現(xiàn)內(nèi)容跟隨小編一起看看吧2021-11-11
springboot啟動(dòng)mongoDB報(bào)錯(cuò)之禁用mongoDB自動(dòng)配置問(wèn)題
這篇文章主要介紹了springboot啟動(dòng)mongoDB報(bào)錯(cuò)之禁用mongoDB自動(dòng)配置問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
泛談Java中的不可變數(shù)據(jù)結(jié)構(gòu)
開發(fā)人員通常認(rèn)為擁有final引用,或者val在Kotlin或Scala中,足以使對(duì)象不可變。這篇博客文章深入研究了不可變引用和不可變數(shù)據(jù)結(jié)構(gòu),下面小編來(lái)和大家一起學(xué)習(xí)它2019-05-05

