日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

硬核艿艿,新鲜出炉,直接带你弄懂 Spring Boot Jar 启动原理!

發布時間:2025/3/21 javascript 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 硬核艿艿,新鲜出炉,直接带你弄懂 Spring Boot Jar 启动原理! 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

摘要: 原創出處 http://www.iocoder.cn/Spring-Boot/jar/ 「芋道源碼」歡迎轉載,保留摘要,謝謝!

  • 1. 概述

  • 2. MANIFEST.MF

  • 3. JarLauncher

  • 4. LaunchedURLClassLoader

  • 666. 彩蛋


大家好,我是艿艿,一個熬夜退役選手~

這兩周很不順心,接連的變化,一臉的懵逼。還好我還有 150000 女粉,我又充滿能量的肝完了本文。

1. 概述

Spring Boot 提供了 Maven 插件?spring-boot-maven-plugin,可以方便的將 Spring Boot 項目打成?jar?包或者?war?包。

考慮到部署的便利性,我們絕大多數 99.99% 的場景下,我們會選擇打成?jar?包。這樣,我們就無需在部署項目的服務器上,配置相應的 Tomcat、Jetty 等 Servlet 容器。

那么,jar?包是如何運行,并啟動 Spring Boot 項目的呢?這個就是本文的目的,一起弄懂 Spring Boot?jar?包的運行原理

下面,我們來打開一個 Spring Boot?jar?包,看看其里面的結構。如下圖所示,一共分成四部分:

Spring Boot?jar?包

?

  • ①?META-INF?目錄:通過?MANIFEST.MF?文件提供?jar?包的元數據,聲明了?jar?的啟動類。

  • ②?org?目錄:為 Spring Boot 提供的?spring-boot-loader?項目,它是?java -jar?啟動 Spring Boot 項目的秘密所在,也是稍后我們將深入了解的部分。

    Spring Boot Loader provides the secret sauce that allows you to build a single jar file that can be launched using?java -jar. Generally you will not need to use?spring-boot-loader?directly, but instead work with the Gradle or Maven plugin.

  • ③?BOOT-INF/lib?目錄:我們 Spring Boot 項目中引入的依賴的?jar?包們。spring-boot-loader?項目很大的一個作用,就是解決?jar?包里嵌套?jar?的情況,如何加載到其中的類。

  • ④?BOOT-INF/classes?目錄:我們在 Spring Boot 項目中 Java 類所編譯的?.class、配置文件等等。

先簡單劇透下,spring-boot-loader?項目需要解決兩個問題:

  • 第一,如何引導執行我們創建的 Spring Boot 應用的啟動類,例如上述圖中的 Application 類。

  • 第二,如何加載?BOOT-INF/class?目錄下的類,以及?BOOT-INF/lib?目錄下內嵌的?jar?包中的類。

下面,尾隨艿艿,一起來抽絲剝繭!

2. MANIFEST.MF

我們來查看?META-INF/MANIFEST.MF?文件,里面的內容如下:

Manifest-Version: 1.0 Implementation-Title: lab-39-demo Implementation-Version: 2.2.2.RELEASE Start-Class: cn.iocoder.springboot.lab39.skywalkingdemo.Application Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Build-Jdk-Spec: 1.8 Spring-Boot-Version: 2.2.2.RELEASE Created-By: Maven Archiver 3.4.0 Main-Class: org.springframework.boot.loader.JarLauncher

它實際是一個?Properties?配置文件,每一行都是一個配置項目。重點來看看兩個配置項:

  • Main-Class?配置項:Java 規定的?jar?包的啟動類,這里設置為?spring-boot-loader?項目的 JarLauncher 類,進行 Spring Boot 應用的啟動。

  • Start-Class?配置項:Spring Boot 規定的啟動類,這里設置為我們定義的 Application 類。

小知識補充:為什么會有?Main-Class/Start-Class?配置項呢?因為我們是通過 Spring Boot 提供的 Maven 插件?spring-boot-maven-plugin?進行打包,該插件將該配置項寫入到?MANIFEST.MF?中,從而能讓?spring-boot-loader?能夠引導啟動 Spring Boot 應用。

可能胖友會有疑惑,Start-Class?對應的 Application 類自帶了?#main(String[] args)?方法,為什么我們不能直接運行會如何呢?我們來簡單嘗試一下哈,控制臺執行如下:

$?java?-classpath?lab-39-demo-2.2.2.RELEASE.jar?cn.iocoder.springboot.lab39.skywalkingdemo.Application 錯誤:?找不到或無法加載主類?cn.iocoder.springboot.lab39.skywalkingdemo.Application

直接找不到 Application 類,因為它在?BOOT-INF/classes?目錄下,不符合 Java 默認的?jar?包的加載規則。因此,需要通過 JarLauncher 啟動加載。

當然實際還有一個更重要的原因,Java 規定可執行器的?jar?包禁止嵌套其它?jar?包。但是我們可以看到?BOOT-INF/lib?目錄下,實際有 Spring Boot 應用依賴的所有?jar?包。因此,spring-boot-loader?項目自定義實現了 ClassLoader 實現類 LaunchedURLClassLoader,支持加載?BOOT-INF/classes?目錄下的?.class?文件,以及?BOOT-INF/lib?目錄下的?jar?包。

3. JarLauncher

JarLauncher 類是針對 Spring Boot?jar?包的啟動類,整體類圖如下所示:

JarLauncher 類圖

?

友情提示:WarLauncher 類,是針對 Spring Boot?war?包的啟動類,后續胖友可以自己瞅瞅,差別并不大哈~

JarLauncher 的源碼比較簡單,如下圖所示:

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);}@Overrideprotected?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);}}

通過?#main(String[] args)?方法,創建 JarLauncher 對象,并調用其?#launch(String[] args)?方法進行啟動。整體的啟動邏輯,其實是由父類 Launcher 所提供,如下圖所示:

Launcher 啟動過程

父類 Launcher 的?#launch(String[] args)?方法,代碼如下:

//?Launcher.javaprotected?void?launch(String[]?args)?throws?Exception?{//?<1>?注冊?URL?協議的處理器JarFile.registerUrlProtocolHandler();//?<2>?創建類加載器ClassLoader?classLoader?=?createClassLoader(getClassPathArchives());//?<3>?執行啟動類的?main?方法launch(args,?getMainClass(),?classLoader); }
  • <1>?處,調用 JarFile 的?#registerUrlProtocolHandler()?方法,注冊 Spring Boot 自定義的 URLStreamHandler 實現類,用于?jar?包的加載讀取。

  • <2>?處,調用自身的?#createClassLoader(List<Archive> archives)?方法,創建自定義的 ClassLoader 實現類,用于從?jar?包中加載類。

  • <3>?處,執行我們聲明的 Spring Boot 啟動類,進行 Spring Boot 應用的啟動。

簡單來說,就是整一個可以讀取?jar?包中類的加載器,保證?BOOT-INF/lib?目錄下的類和?BOOT-classes?內嵌的?jar?中的類能夠被正常加載到,之后執行 Spring Boot 應用的啟動。

下面,我們逐行代碼來看看噢。即將代碼多多,保持淡定,嘿嘿~

3.1 registerUrlProtocolHandler

友情提示:對應?JarFile.registerUrlProtocolHandler();?代碼段,不要迷路。

JarFile 是?java.util.jar.JarFile?的子類,如下所示:

public?class?JarFile?extends?java.util.jar.JarFile?{//?...?省略其它代碼}

JarFile 主要增強支持對內嵌的?jar?包的獲取。如下圖所示:

讀取內嵌的?jar?包的演示

OK,介紹完之后,讓我們回到 JarFile 的?#registerUrlProtocolHandler()?方法,注冊 Spring Boot 自定義的 URL 協議的處理器。代碼如下:

//?JarFile.javaprivate?static?final?String?PROTOCOL_HANDLER?=?"java.protocol.handler.pkgs";private?static?final?String?HANDLERS_PACKAGE?=?"org.springframework.boot.loader";/***?Register?a?{@literal?'java.protocol.handler.pkgs'}?property?so?that?a*?{@link?URLStreamHandler}?will?be?located?to?deal?with?jar?URLs.*/ public?static?void?registerUrlProtocolHandler()?{//?獲得?URLStreamHandler?的路徑String?handlers?=?System.getProperty(PROTOCOL_HANDLER,?"");//?將?Spring?Boot?自定義的?HANDLERS_PACKAGE(org.springframework.boot.loader)?補充上去System.setProperty(PROTOCOL_HANDLER,?("".equals(handlers)???HANDLERS_PACKAGE:?handlers?+?"|"?+?HANDLERS_PACKAGE));//?重置已緩存的?URLStreamHandler?處理器們resetCachedUrlHandlers(); }/***?Reset?any?cached?handlers?just?in?case?a?jar?protocol?has?already?been?used.*?We?reset?the?handler?by?trying?to?set?a?null?{@link?URLStreamHandlerFactory}?which*?should?have?no?effect?other?than?clearing?the?handlers?cache.**?重置?URL?中的?URLStreamHandler?的緩存,防止?`jar://`?協議對應的?URLStreamHandler?已經創建*?我們通過設置?URLStreamHandlerFactory?為?null?的方式,清空?URL?中的該緩存。*/ private?static?void?resetCachedUrlHandlers()?{try?{URL.setURLStreamHandlerFactory(null);}?catch?(Error?ex)?{//?Ignore} }
  • 胖友先跟著注釋,自己閱讀下如上的代碼~

目的很明確,通過將?org.springframework.boot.loader?包設置到?"java.protocol.handler.pkgs"?環境變量,從而使用到自定義的 URLStreamHandler 實現類 Handler,處理?jar:?協議的 URL。

友情提示:這里我們暫時不深入 Handler 的源碼,避免直接走的太深,丟失了主干。后續胖友可結合《Java URL 協議擴展實現》文章,進行 Handler 的實現理解。

另外,HandlerTests 提供的單元測試,也是非常有幫助的~

3.2 createClassLoader

友情提示:對應?ClassLoader classLoader = createClassLoader(getClassPathArchives())?代碼段,不要迷路。

3.2.1 getClassPathArchives

首先,我們先來看看?#getClassPathArchives()?方法,它是由 ExecutableArchiveLauncher 所實現,代碼如下:

//?ExecutableArchiveLauncher.javaprivate?final?Archive?archive;@Override protected?List<Archive>?getClassPathArchives()?throws?Exception?{//?<1>?獲得所有?ArchiveList<Archive>?archives?=?new?ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));//?<2>?后續處理postProcessClassPathArchives(archives);return?archives; }protected?abstract?boolean?isNestedArchive(Archive.Entry?entry);protected?void?postProcessClassPathArchives(List<Archive>?archives)?throws?Exception?{ } “

友情提示:這里我們會看到一個 Archive 對象,先可以暫時理解成一個一個的檔案,稍后會清晰認識的~

<1>?處,this::isNestedArchive?代碼段,創建了 EntryFilter 匿名實現類,用于過濾?jar?包不需要的目錄。

//?Archive.java/***?Represents?a?single?entry?in?the?archive.*/ interface?Entry?{/***?Returns?{@code?true}?if?the?entry?represents?a?directory.*?@return?if?the?entry?is?a?directory*/boolean?isDirectory();/***?Returns?the?name?of?the?entry.*?@return?the?name?of?the?entry*/String?getName();}/***?Strategy?interface?to?filter?{@link?Entry?Entries}.*/ interface?EntryFilter?{/***?Apply?the?jar?entry?filter.*?@param?entry?the?entry?to?filter*?@return?{@code?true}?if?the?filter?matches*/boolean?matches(Entry?entry);}

這里在它的內部,調用了?#isNestedArchive(Archive.Entry entry)?方法,它是由 JarLauncher 所實現,代碼如下:

//?JarLauncher.javastatic?final?String?BOOT_INF_CLASSES?=?"BOOT-INF/classes/";static?final?String?BOOT_INF_LIB?=?"BOOT-INF/lib/";@Override protected?boolean?isNestedArchive(Archive.Entry?entry)?{//?如果是目錄的情況,只要?BOOT-INF/classes/?目錄if?(entry.isDirectory())?{return?entry.getName().equals(BOOT_INF_CLASSES);}//?如果是文件的情況,只要?BOOT-INF/lib/?目錄下的?`jar`?包return?entry.getName().startsWith(BOOT_INF_LIB); }
  • 目的就是過濾獲得,BOOT-INF/classes/?目錄下的類,以及?BOOT-INF/lib/?的內嵌?jar?包。

<1>?處,this.archive.getNestedArchives?代碼段,調用 Archive 的?#getNestedArchives(EntryFilter filter)?方法,獲得?archive?內嵌的 Archive 集合。代碼如下:

//?Archive.java/***?Returns?nested?{@link?Archive}s?for?entries?that?match?the?specified?filter.*?@param?filter?the?filter?used?to?limit?entries*?@return?nested?archives*?@throws?IOException?if?nested?archives?cannot?be?read*/ List<Archive>?getNestedArchives(EntryFilter?filter)?throws?IOException;

Archive 接口,是?spring-boot-loader?項目定義的檔案抽象,其子類如下圖所示:

Archive 類圖

?

  • ExplodedArchive 是針對目錄的 Archive 實現類。

  • JarFileArchive 是針對?jar?包的 Archive 實現類。

友情提示:這塊可能有一丟丟復雜,胖友吃耐心哈~

那么,我們在 ExecutableArchiveLauncher 的?archive?屬性是怎么來的呢?答案在 ExecutableArchiveLauncher 的構造方法中,代碼如下:

//?ExecutableArchiveLauncher.javapublic?abstract?class?ExecutableArchiveLauncher?extends?Launcher?{private?final?Archive?archive;public?ExecutableArchiveLauncher()?{try?{this.archive?=?createArchive();}?catch?(Exception?ex)?{throw?new?IllegalStateException(ex);}}protected?ExecutableArchiveLauncher(Archive?archive)?{this.archive?=?archive;}//?...?省略其它 }//?Launcher.java public?abstract?class?Launcher?{protected?final?Archive?createArchive()?throws?Exception?{//?獲得?jar?所在的絕對路徑ProtectionDomain?protectionDomain?=?getClass().getProtectionDomain();CodeSource?codeSource?=?protectionDomain.getCodeSource();URI?location?=?(codeSource?!=?null)???codeSource.getLocation().toURI()?:?null;String?path?=?(location?!=?null)???location.getSchemeSpecificPart()?:?null;if?(path?==?null)?{throw?new?IllegalStateException("Unable?to?determine?code?source?archive");}File?root?=?new?File(path);if?(!root.exists())?{throw?new?IllegalStateException("Unable?to?determine?code?source?archive?from?"?+?root);}//?如果是目錄,則使用?ExplodedArchive?進行展開//?如果不是目錄,則使用?JarFileArchivereturn?(root.isDirectory()???new?ExplodedArchive(root):?new?JarFileArchive(root));}}

根據根路徑是否為目錄的情況,創建 ExplodedArchive 或 JarFileArchive 對象。那么問題就來了,這里的?root?是什么呢?艿艿一波騷操作,終于輸出了答案,如下圖所示:

root?是什么!

root?路徑為?jar?包的絕對地址,也就是說創建 JarFileArchive 對象。原因是,Launcher 所在包為?org?下,它的根目錄當然是?jar?包的絕對路徑哈!

😈 現在是不是對 Archive 稍微有點感覺落?繼續附加如下代碼,打印 JarFileArchive 的?#getNestedArchives(EntryFilter filter)?方法的執行結果。

//?==========?附加代碼: //?創建?Archive?對象 Archive?archive?=?new?JarFileArchive(root); //?創建?EntryFilter?對象 Archive.EntryFilter?filter?=?new?Archive.EntryFilter()?{static?final?String?BOOT_INF_CLASSES?=?"BOOT-INF/classes/";static?final?String?BOOT_INF_LIB?=?"BOOT-INF/lib/";@Overridepublic?boolean?matches(Archive.Entry?entry)?{//?如果是目錄的情況,只要?BOOT-INF/classes/?目錄if?(entry.isDirectory())?{return?entry.getName().equals(BOOT_INF_CLASSES);}//?如果是文件的情況,只要?BOOT-INF/lib/?目錄下的?`jar`?包return?entry.getName().startsWith(BOOT_INF_LIB);}}; //?執行讀取 for?(Archive?item?:?archive.getNestedArchives(filter))?{System.out.println(item.getUrl()); }//?==========?執行結果: jar:file:/Users/yunai/Java/SpringBoot-Labs/lab-39/lab-39-demo/target/lab-39-demo-2.2.2.RELEASE.jar!/BOOT-INF/classes!/ jar:file:/Users/yunai/Java/SpringBoot-Labs/lab-39/lab-39-demo/target/lab-39-demo-2.2.2.RELEASE.jar!/BOOT-INF/lib/spring-boot-starter-web-2.2.2.RELEASE.jar!/ jar:file:/Users/yunai/Java/SpringBoot-Labs/lab-39/lab-39-demo/target/lab-39-demo-2.2.2.RELEASE.jar!/BOOT-INF/lib/spring-boot-starter-2.2.2.RELEASE.jar!/ jar:file:/Users/yunai/Java/SpringBoot-Labs/lab-39/lab-39-demo/target/lab-39-demo-2.2.2.RELEASE.jar!/BOOT-INF/lib/spring-boot-2.2.2.RELEASE.jar!/ ...?省略其他?jar?包

從執行結果可以看出,BOOT-INF/classes/?目錄被歸類為一個?Archive 對象,而?BOOT-INF/lib/?目錄下的每個內嵌?jar?包都對應一個?Archive 對象。

來來來,回過頭來看看 JarFileArchive 的?#getNestedArchives(EntryFilter filter)?方法的源碼,如下圖所示:

JarFileArchive 的?#getNestedArchives(EntryFilter filter)?方法

現在是不是明白了噢!良心如我,哈哈哈!

友情提示:上述的測試代碼,可以訪問 lab-39-demo 項目查看。

3.2.2 createClassLoader

然后,我再來看看?#createClassLoader(List<Archive> archives)?方法,它是由 ExecutableArchiveLauncher 所實現,代碼如下:

//?ExecutableArchiveLauncher.javaprotected?ClassLoader?createClassLoader(List<Archive>?archives)?throws?Exception?{//?獲得所有?Archive?的?URL?地址List<URL>?urls?=?new?ArrayList<>(archives.size());for?(Archive?archive?:?archives)?{urls.add(archive.getUrl());}//?創建加載這些?URL?的?ClassLoaderreturn?createClassLoader(urls.toArray(new?URL[0])); }protected?ClassLoader?createClassLoader(URL[]?urls)?throws?Exception?{return?new?LaunchedURLClassLoader(urls,?getClass().getClassLoader()); }

基于獲得的 Archive 數組,創建自定義 ClassLoader 實現類 LaunchedURLClassLoader,通過它來加載?BOOT-INF/classes?目錄下的類,以及?BOOT-INF/lib?目錄下的?jar?包中的類。

進一步的解析,我們在「5. LaunchedURLClassLoader」小節中,進行分享哈!

3.3 launch

友情提示:對應?launch(args, getMainClass(), classLoader)?代碼段,不要迷路。

3.3.1 getMainClass

首先,我們先來看看#getMainClass()?方法,它是由 ExecutableArchiveLauncher 所實現,代碼如下:

//?ExecutableArchiveLauncher.java@Override protected?String?getMainClass()?throws?Exception?{//?獲得啟動的類的全名Manifest?manifest?=?this.archive.getManifest();String?mainClass?=?null;if?(manifest?!=?null)?{mainClass?=?manifest.getMainAttributes().getValue("Start-Class");}if?(mainClass?==?null)?{throw?new?IllegalStateException("No?'Start-Class'?manifest?entry?specified?in?"?+?this);}return?mainClass; }

從?jar?包的?MANIFEST.MF?文件的?Start-Class?配置項,,獲得我們設置的 Spring Boot 的啟動類。

3.3.2 createMainMethodRunner

然后,我們再來看看?#launch()?方法,它是由 Launcher 所實現,代碼如下:

protected?void?launch(String[]?args,?String?mainClass,?ClassLoader?classLoader)throws?Exception?{//?<1>?設置?LaunchedURLClassLoader?作為類加載器Thread.currentThread().setContextClassLoader(classLoader);//?<2>?創建?MainMethodRunner?對象,并執行?run?方法,啟動?Spring?Boot?應用createMainMethodRunner(mainClass,?args,?classLoader).run(); }

該方法負責最終的 Spring Boot 應用真正的啟動

  • <1>?處:設置「3.2.2 createClassLoader」創建的 LaunchedURLClassLoader 作為類加載器,從而保證能夠從?jar?加載到相應的類。

  • <2>?處,調用?#createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader)?方法,創建 MainMethodRunner 對象,并執行其?#run()?方法來啟動 Spring Boot 應用。

下面,我們來看看?MainMethodRunner?類,負責 Spring Boot 應用的啟動。代碼如下:

public?class?MainMethodRunner?{private?final?String?mainClassName;private?final?String[]?args;/***?Create?a?new?{@link?MainMethodRunner}?instance.*?@param?mainClass?the?main?class*?@param?args?incoming?arguments*/public?MainMethodRunner(String?mainClass,?String[]?args)?{this.mainClassName?=?mainClass;this.args?=?(args?!=?null)???args.clone()?:?null;}public?void?run()?throws?Exception?{//?<1>?加載?Spring?BootClass<?>?mainClass?=?Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);//?<2>?反射調用?main?方法Method?mainMethod?=?mainClass.getDeclaredMethod("main",?String[].class);mainMethod.invoke(null,?new?Object[]?{?this.args?});}}
  • <1>?處:通過 LaunchedURLClassLoader 類加載器,加載到我們設置的 Spring Boot 的主啟動類。

  • <2>?處:通過反射調用主啟動類的?#main(String[] args)?方法,啟動 Spring Boot 應用。這里也告訴了我們答案,為什么我們通過編寫一個帶有?#main(String[] args)?方法的類,就能夠啟動 Spring Boot 應用。

4. LaunchedURLClassLoader

LaunchedURLClassLoader 是?spring-boot-loader?項目自定義的類加載器,實現對?jar?包中?META-INF/classes?目錄下的和?META-INF/lib?內嵌的?jar?包中的加載

FROM 《維基百科 —— Java 類加載器》

Java 類加載器是 Java 運行時環境的一個部件,負責動態加載 Java 類到 Java 虛擬機的內存空間中。類通常是按需加載,即第一次使用該類時才加載。

由于有了類加載器,Java 運行時系統不需要知道文件與文件系統。對學習類加載器而言,掌握 Java 的委派概念是很重要的。每個 Java 類必須由某個類加載器裝入到內存。

在「3.2.2 createClassLoader」小節中,我們可以看到 LaunchedURLClassLoader 的創建代碼如下:

//?ExecutableArchiveLauncher.javaprotected?ClassLoader?createClassLoader(List<Archive>?archives)?throws?Exception?{//?獲得所有?Archive?的?URL?地址List<URL>?urls?=?new?ArrayList<>(archives.size());for?(Archive?archive?:?archives)?{urls.add(archive.getUrl());}//?創建加載這些?URL?的?ClassLoaderreturn?createClassLoader(urls.toArray(new?URL[0])); }protected?ClassLoader?createClassLoader(URL[]?urls)?throws?Exception?{return?new?LaunchedURLClassLoader(urls,?getClass().getClassLoader()); }//?LaunchedURLClassLoader.javapublic?class?LaunchedURLClassLoader?extends?URLClassLoader?{public?LaunchedURLClassLoader(URL[]?urls,?ClassLoader?parent)?{super(urls,?parent);}}
  • 第一個參數?urls,使用的是 Archive 集合對應的 URL 地址們,從而告訴 LaunchedURLClassLoader 讀取?jar?的地址

  • 第二個參數?parent,設置 LaunchedURLClassLoader 的加載器。這里后續胖友可以理解下,類加載器的雙親委派模型,這里就拓展開了。

LaunchedURLClassLoader 的實現代碼并不多,我們主要來看看它是如何從?jar?包中加載類的。核心如下圖所示:

  • <1>?處,在通過父類的?#getPackage(String name)?方法獲取不到指定類所在的包時,會通過遍歷?urls?數組,從?jar?包中加載類所在的包。當找到包時,會調用?#definePackage(String name, Manifest man, URL url)?方法,設置包所在的?Archive?對應的?url。

  • <2>?處,調用父類的?#loadClass(String name, boolean resolve)?方法,加載對應的類。

如此,我們就實現了通過 LaunchedURLClassLoader 加載?jar?包中內嵌的類。

666. 彩蛋

總體來說,Spring Boot?jar?啟動的原理是非常清晰的,整體如下圖所示:

Spring Boot?jar?啟動原理

紅色部分,解決?jar?包中的類加載問題:

  • 通過 Archive,實現?jar?包的遍歷,將?META-INF/classes?目錄和?META-INF/lib?的每一個內嵌的?jar?解析成一個 Archive 對象。

  • 通過 Handler,處理?jar:?協議的 URL 的資源讀取,也就是讀取了每個 Archive 里的內容。

  • 通過 LaunchedURLClassLoader,實現?META-INF/classes?目錄下的類和?META-INF/classes?目錄下內嵌的?jar?包中的類的加載。具體的 URL 來源,是通過 Archive 提供;具體 URL 的讀取,是通過 Handler 提供。

橘色部分,解決 Spring Boot 應用的啟動問題:

  • 通過 MainMethodRunner ,實現 Spring Boot 應用的啟動類的執行。

當然,上述的一切都是通過 Launcher 來完成引導和啟動,通過?MANIFEST.MF?進行具體配置。

😈 生活如此美好,本文就此結束!


另外,本文有兩個部分,胖友可以自己再去擼一擼,玩一玩:

  • WarLauncher 類:實現 Spring Boot?war?包的啟動。

  • org.springframework.boot.loader.jar?包:具體讀取?jar?的邏輯。

總結

以上是生活随笔為你收集整理的硬核艿艿,新鲜出炉,直接带你弄懂 Spring Boot Jar 启动原理!的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。