欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot應(yīng)用jar包啟動原理詳解

 更新時(shí)間:2022年03月31日 15:26:25   作者:禿頭愛健身  
本文主要介紹了SpringBoot應(yīng)用jar包啟動原理詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

1、maven打包

Spring Boot項(xiàng)目的pom.xml文件中默認(rèn)使用spring-boot-maven-plugin插件進(jìn)行打包:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

在執(zhí)行完maven clean package之后,會生成來個jar相關(guān)文件:

  • test-0.0.1-SNAPSHOT.jar
  • test-0.0.1-SNAPSHOT.jar.original

2、Jar包目錄結(jié)構(gòu)

以筆者的test-0.0.1-SNAPSHOT.jar為例,來看一下jar的目錄結(jié)構(gòu),其中都包含哪些目錄和文件?

請?zhí)砑訄D片描述

可以概述為:

spring-boot-learn-0.0.1-SNAPSHOT
├── META-INF
│ └── MANIFEST.MF
├── BOOT-INF
│ ├── classes
│ │ └── 應(yīng)用程序
│ └── lib
│ └── 第三方依賴jar
└── org
└── springframework
└── boot
└── loader
└── springboot啟動程序

其中主要包括三大目錄:META-INF、BOOT-INF、org。

1)META-INF內(nèi)容

META-INF記錄了相關(guān)jar包的基礎(chǔ)信息,包括:入口程序。具體內(nèi)容如下:

Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: tms-start
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.saint.StartApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.4.5
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher

  • Main-Classorg.springframework.boot.loader.JarLauncher,即jar啟動的Main函數(shù);
  • Start-Classcom.saint.StartApplication,即我們自己SpringBoot項(xiàng)目的啟動類;也是下文提到的項(xiàng)目的引導(dǎo)類。

2)BOOT-INF內(nèi)容

  • BOOT-INF/classes目錄:存放應(yīng)用編譯后的class文件源碼;
  • BOOT-INF/lib目錄:存放應(yīng)用依賴的所有三方j(luò)ar包文件;

3)org內(nèi)容

org目錄下存放著所有SpringBoot相關(guān)的class文件,比如:JarLauncher、LaunchedURLClassLoader。

請?zhí)砑訄D片描述

3、可執(zhí)行Jar(JarLauncher)

從jar包內(nèi)META-INF/MANIFEST.MF文件中的Main-Class屬性值為org.springframework.boot.loader.JarLauncher,可以看出main函數(shù)是JarLauncher,即:SpringBoot應(yīng)用中的Main-class屬性指向的class為org.springframework.boot.loader.JarLauncher

其實(shí)吧,主要是 Java官方文檔規(guī)定:java -jar命令引導(dǎo)的具體啟動類必須配置在MANIFEST.MF資源的Main-class屬性中;又根據(jù)“JAR文件規(guī)范”,MANIFEST.MF資源必須存放在/META-INF/目錄下。所以main函數(shù)才是JarLauncher

JarLauncher類繼承圖如下:

請?zhí)砑訄D片描述

JarLauncher的類注釋我們看出JarLauncher的作用:

  • 加載內(nèi)部/BOOT-INF/lib下的所有三方依賴jar;
  • 加載內(nèi)部/BOOT-INF/classes下的所有應(yīng)用class;

1)JarLauncher的運(yùn)行步驟?

  • 在解壓jar包后的根目錄下運(yùn)行 java org.springframework.boot.loader.JarLauncher。項(xiàng)目引導(dǎo)類(META-INF/MANIFEST.MF文件中的Start-Class屬性)被JarLauncher加載并執(zhí)行。
  • 如果直接運(yùn)行Start-Class(示例的StartApplication)類,會報(bào)錯ClassNotFoundException。
  • Spring Boot依賴的JAR文件均存放在BOOT-INF/lib目錄下。JarLauncher會將這些JAR文件作為Start-Class的類庫依賴。

這也是為什么JarLauncher能夠引導(dǎo),而直接運(yùn)行Start-Class卻不行。

2)JarLauncher實(shí)現(xiàn)原理?

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#main()中新建了JarLauncher并調(diào)用父類Launcher中的launch()方法啟動程序;

  • BOOT_INF_CLASSES、BOOT_INF_LIB變量對應(yīng)BOOT-INF/classes和lib路徑;
  • isNestedArchive(Archinve.Entry entry)方法用于判斷FAT JAR資源的相對路徑是否為nestedArchive嵌套文檔。進(jìn)而決定這些FAT JAR是否會被launch。 當(dāng)方法返回false時(shí),說明FAT JAR被解壓至文件目錄。

1> Archive的概念

archive即歸檔文件,這個概念在linux下比較常見;通常就是一個tar/zip格式的壓縮包;而jar正是zip格式的。

SpringBoot抽象了Archive的概念,一個Archive可以是jar(JarFileArchive),也可以是文件目錄(ExplodedArchive);這樣也就統(tǒng)一了訪問資源的邏輯層;

public interface Archive extends Iterable<Archive.Entry>, AutoCloseable {
    ....
}

Archive繼承自Archive.Entry,Archive.Entry有兩種實(shí)現(xiàn):

JarFileArchive.JarFileEntry --> 基于java.util.jar.JarEntry實(shí)現(xiàn),表示FAT JAR嵌入資源。

ExplodedArchive.FileEntry --> 基于文件系統(tǒng)實(shí)現(xiàn);

兩者的主要差別是ExplodedArchive相比于JarFileArchive多了一個獲取文件的getFile()方法;

public File getFile() {
    return this.file;
}

也就是說一個在jar包環(huán)境下尋找資源,一個在文件夾目錄下尋找資源;

所以從實(shí)現(xiàn)層面證明了JarLauncher支持JAR和文件系統(tǒng)兩種啟動方式。

當(dāng)執(zhí)行java -jar命令時(shí),將調(diào)用/META-INF /MANIFEST.MF文件的Main-Class屬性的main()方法,實(shí)際上調(diào)用的是JarLauncher#launch(args)方法;

3) Launcher#launch(args)方法

protected void launch(String[] args) throws Exception {
    if (!isExploded()) {
        // phase1:注冊jar URL處理器
        JarFile.registerUrlProtocolHandler();
    }
    // phase2:創(chuàng)建ClassLoader
    ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
    String jarMode = System.getProperty("jarmode");
    String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
    // phase3:調(diào)用實(shí)際的引導(dǎo)類launch
    launch(args, launchClass, classLoader);
}

launch()方法分三步:

  • 注冊jar URL處理器;
  • 為所有的Archive創(chuàng)建可以加載jar in jar目錄的ClassLoader;
  • 調(diào)用實(shí)際的引導(dǎo)類(Start-Class);

1> phase1 注冊jar URL處理器

private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs";

	private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader";

public static void registerUrlProtocolHandler() {
    String handlers = System.getProperty(PROTOCOL_HANDLER, "");
    System.setProperty(PROTOCOL_HANDLER, ("".equals(handlers) ? HANDLERS_PACKAGE
                                          : handlers + "|" + HANDLERS_PACKAGE));
    // 重置緩存的UrlHandlers;
    resetCachedUrlHandlers();
}

private static void resetCachedUrlHandlers() {
    try {
        // 由URL類實(shí)現(xiàn):通過URL.setURLStreamHandlerFactory()獲得URLStreamHandler。
        URL.setURLStreamHandlerFactory(null);
    }
    catch (Error ex) {
        // Ignore
    }
}

JarFile#resetCachedUrlHandlers()方法利用java.net.URLStreamHandler擴(kuò)展機(jī)制,實(shí)現(xiàn)由URL#getURLStreamHandler(String)提供。

URL#getURLStreamHandler(String protocol)方法:

首先,URL的關(guān)聯(lián)協(xié)議(Protocol)對應(yīng)一種URLStreamHandler實(shí)現(xiàn)類。

JDK內(nèi)建了一些協(xié)議的實(shí)現(xiàn),這些實(shí)現(xiàn)均存放在sun.net.www.protocol包下,并且類名必須為Handler,其類全名模式為sun.net.www.protocol.${protocol}.Handler(包名前綴.協(xié)議名.Handler),其中${protocol}表示協(xié)議名。

如果需要擴(kuò)展,則必須繼承URLStreamHandler類,通過配置Java系統(tǒng)屬性java.protocol.handler.pkgs,追加URLStreamHandler實(shí)現(xiàn)類的package,多個package以“|”分割。

所以對于SpringBoot的JarFile,registerURLProtocolHandler()方法將package org.springframework.boot.loader追加到j(luò)ava系統(tǒng)屬性java.protocol.handler.pkgs中。

也就是說,org.springframework.boot.loader包下存在協(xié)議對應(yīng)的Handler類,即org.springframework.boot.loader.jar.Handler;并且按照類名模式,其實(shí)現(xiàn)協(xié)議為JAR。

另外:在URL#getURLStreamHandler()方法中,處理器先讀取Java系統(tǒng)屬性java.protocol.handler.pkgs,無論其是否存在,繼續(xù)讀取sun.net.www.protocol包;所以JDK內(nèi)建URLStreamHandler實(shí)現(xiàn)是兜底的。

為什么SpringBoot要選擇覆蓋URLStreamHandler?

  • Spring BOOT FAT JAR除包含傳統(tǒng)Java Jar資源之外,還包含依賴的JAR文件;即存在jar in jar的情況;
  • 默認(rèn)情況下,JDK提供的ClassLoader只能識別jar中的class文件以及加載classpath下的其他jar包中的class文件,對于jar in jar的包無法加載;
  • 當(dāng)SpringBoot FAT JAR被java -jar命令引導(dǎo)時(shí),其內(nèi)部的JAR文件無法被內(nèi)嵌實(shí)現(xiàn)sun.net.www.protocol.jar.Handler當(dāng)做class Path,故需要定義了一套URLStreamHandler實(shí)現(xiàn)類和JarURLConnection實(shí)現(xiàn)類,用來加載jar in jar包的class類文件;

2> phase2 創(chuàng)建可以加載jar in jar目錄的ClassLoader

獲取所有的Archive,然后針對每個Archive分別創(chuàng)建ClassLoader;

ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());

/**
 * 獲取所有的Archive(包含jar in jar的情況)
 */
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
    return getClassPathArchives().iterator();
}

/**
 * 針對每個Archive分別創(chuàng)建ClassLoader
 */
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
    List<URL> urls = new ArrayList<>(archives.size());
    for (Archive archive : archives) {
        urls.add(archive.getUrl());
    }
    return createClassLoader(urls.toArray(new URL[0]));
}

3> phase3 調(diào)用實(shí)際的引導(dǎo)類(Start-Class)

// case1: 通過ExecutableArchiveLauncher#getMainClass()獲取MainClass
String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
// 2、運(yùn)行實(shí)際的引導(dǎo)類
launch(args, launchClass, classLoader);

對于phase3,大致可以分為兩步:

  • 首先通過ExecutableArchiveLauncher#getMainClass()獲取mainClass(即:/META-INF/MANIFEST.MF資源中的Start-Class屬性);
  • 利用反射獲取mainClass類中的main(Stirng[])方法并調(diào)用;

<1> 獲取mainClass:

在這里插入圖片描述

Start-Class屬性來自/META_INF/MANIFEST.MF資源中。Launcher的子類JarLauncherWarLauncher沒有實(shí)現(xiàn)getMainClass()方法。所以無論是Jar還是War,讀取的SpringBoot啟動類均來自此屬性。

<2> 執(zhí)行mainClass的main()方法:

獲取mainClass之后,MainMethodRunner#run()方法利用反射獲取mainClass類中的main(Stirng[])方法并調(diào)用。

在這里插入圖片描述

運(yùn)行JarLauncher實(shí)際上是在同進(jìn)程、同線程內(nèi)調(diào)用Start-Class類的main(Stirng[])方法,并且在調(diào)用前準(zhǔn)備好Class Path。

4、WarLauncher

WarLauncher是可執(zhí)行WAR的啟動器。

WarLauncher與JarLauncher的差異很小,主要區(qū)別在于項(xiàng)目文件和JAR Class Path路徑的不同。

  • 相比于FAT Jar的目錄,WAR增加了WEB-INF/lib-provided,并且該目錄僅存放<scope>provided</scope>的JAR文件。
  • 傳統(tǒng)的Servlet應(yīng)用的Class Path路徑僅關(guān)注WEB-INF/classes/和WEB-INF/lib/目錄,因此WEB-INF/lib-provided/中的JAR將被Servlet忽略。

好處:打包后的WAR文件能夠在Servlet容器中兼容運(yùn)行。

所以JarLauncher和WarLauncher并無本質(zhì)區(qū)別。

5、總結(jié)

Spring Boot應(yīng)用Jar/War的啟動流程:

Spring Boot應(yīng)用打包之后,生成一個Fat jar,包含了應(yīng)用依賴的所有三方j(luò)ar包和SpringBoot Loader相關(guān)的類。

Fat jar的啟動Main函數(shù)是JarLauncher,它負(fù)責(zé)創(chuàng)建一個LaunchedURLClassLoader來加載BOOT-INF/classes目錄以及/BOOT-INF/lib下面的jar,并利用反射獲取mainClass類中的main(Stirng[])方法并調(diào)用。即:運(yùn)行JarLauncher實(shí)際上是在同進(jìn)程、同線程內(nèi)調(diào)用Start-Class類的main(Stirng[])方法,并且在調(diào)用前準(zhǔn)備好Class Path。

其他點(diǎn):

SpringBoot通過擴(kuò)展JarFile、JarURLConnection及URLStreamHandler,實(shí)現(xiàn)了jar in jar中資源的加載。

SpringBoot通過擴(kuò)展URLClassLoader --> LauncherURLClassLoader,實(shí)現(xiàn)了jar in jar中class文件的加載。

WarLauncher相比JarLauncher只是多加載WEB-INF/lib-provided目錄下的jar文件。

到此這篇關(guān)于SpringBoot應(yīng)用jar包啟動原理詳解的文章就介紹到這了,更多相關(guān)SpringBoot jar包啟動內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 深入理解Spring的事務(wù)傳播行為

    深入理解Spring的事務(wù)傳播行為

    spring特有的事務(wù)傳播行為,spring支持7種事務(wù)傳播行為,確定客戶端和被調(diào)用端的事務(wù)邊界(說得通俗一點(diǎn)就是多個具有事務(wù)控制的service的相互調(diào)用時(shí)所形成的復(fù)雜的事務(wù)邊界控制),這篇文章主要給大家介紹了關(guān)于Spring事務(wù)傳播行為的相關(guān)資料,需要的朋友可以參考下。
    2018-02-02
  • SpringBoot 返回Html界面的操作代碼

    SpringBoot 返回Html界面的操作代碼

    這篇文章主要介紹了SpringBoot 返回Html界面的相關(guān)資料,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • 關(guān)于Spring源碼深度解析(AOP功能源碼解析)

    關(guān)于Spring源碼深度解析(AOP功能源碼解析)

    這篇文章主要介紹了關(guān)于Spring源碼深度解析(AOP功能源碼解析),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • SpringBoot?整合Security權(quán)限控制的初步配置

    SpringBoot?整合Security權(quán)限控制的初步配置

    這篇文章主要為大家介紹了SpringBoot?整合Security權(quán)限控制的初步配置實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • java 如何從字符串里面提取時(shí)間

    java 如何從字符串里面提取時(shí)間

    這篇文章主要介紹了java實(shí)現(xiàn)從字符串里面提取時(shí)間的方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • SpringBoot 普通類調(diào)用Bean對象的一種方式推薦

    SpringBoot 普通類調(diào)用Bean對象的一種方式推薦

    這篇文章主要介紹了SpringBoot 普通類調(diào)用Bean對象的一種方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java實(shí)現(xiàn)Redis哨兵的示例代碼

    Java實(shí)現(xiàn)Redis哨兵的示例代碼

    這篇文章主要介紹了Java實(shí)現(xiàn)Redis哨兵的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Java 程序員容易犯的10個SQL錯誤

    Java 程序員容易犯的10個SQL錯誤

    本文介紹了Java 程序員容易犯的10個SQL錯誤。具有很好的參考價(jià)值,下面跟著小編一起來看下吧
    2017-01-01
  • mybaits-plus?lambdaQuery()?和?lambdaUpdate()?常見的使用方法

    mybaits-plus?lambdaQuery()?和?lambdaUpdate()?常見的使用方法

    MyBatis-Plus是一個?MyBatis?(opens?new?window)的增強(qiáng)工具,在?MyBatis?的基礎(chǔ)上只做增強(qiáng)不做改變,為簡化開發(fā)、提高效率而生,這篇文章主要介紹了mybaits-plus?lambdaQuery()?和?lambdaUpdate()?比較常見的使用方法,需要的朋友可以參考下
    2023-01-01
  • 深入理解Java中線程間的通信

    深入理解Java中線程間的通信

    一般來講,線程內(nèi)部有自己私有的線程上下文,互不干擾。但是當(dāng)我們需要多個線程之間相互協(xié)作的時(shí)候,就需要我們掌握J(rèn)ava線程的通信方式。本文將介紹Java線程之間的幾種通信原理,需要的可以參考一下
    2022-11-11

最新評論