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

springboot的jar能獨(dú)立運(yùn)行的原因解析

 更新時(shí)間:2020年12月25日 08:33:52   作者:zq2599  
這篇文章主要介紹了springboot的jar能獨(dú)立運(yùn)行的原因解析,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

歡迎訪問(wèn)我的GitHub

https://github.com/zq2599/blog_demos

內(nèi)容:所有原創(chuàng)文章分類匯總及配套源碼,涉及Java、Docker、Kubernetes、DevOPS等;

歡迎訪問(wèn)我的GitHub

https://github.com/zq2599/blog_demos
內(nèi)容:所有原創(chuàng)文章分類匯總及配套源碼,涉及Java、Docker、Kubernetes、DevOPS等;

能獨(dú)立運(yùn)行的jar文件

在開(kāi)發(fā)springboot應(yīng)用時(shí),通過(guò)java -jar命令啟動(dòng)應(yīng)用是常用的方式,今天就來(lái)一起了解這個(gè)簡(jiǎn)單操作背后的技術(shù);

開(kāi)發(fā)demo

開(kāi)發(fā)一個(gè)springboot應(yīng)用作為本次研究的對(duì)象,對(duì)應(yīng)的版本信息如下:

  • JDK:1.8.0_211
  • springboot:2.3.1.RELEASE
  • maven:3.6.0

接下來(lái)開(kāi)發(fā)springboot應(yīng)用,這個(gè)應(yīng)用非常簡(jiǎn)單:

springboot應(yīng)用名為springbootstarterdemo,pom.xml文件內(nèi)容:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.bolingcavalry</groupId>
  <artifactId>springbootstarterdemo</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>springbootstarterdemo</name>
  <description>Demo project for Spring Boot</description>
  <properties>
    <java.version>1.8</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

只有一個(gè)java類,里面有個(gè)http接口:

package com.bolingcavalry.springbootstarterdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@SpringBootApplication
@RestController
public class SpringbootstarterdemoApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringbootstarterdemoApplication.class, args);
  }
  @RequestMapping(value = "/hello")
  public String hello(){
    return "hello " + new Date();
  }
}

 

編碼完成,在pom.xml所在目錄執(zhí)行命令

mvn clean package -U -DskipTests

構(gòu)建成功后,在target目錄下得到文件springbootstarterdemo-0.0.1-SNAPSHOT.jar就是這個(gè)springbootstarterdemo-0.0.1-SNAPSHOT.jar,此時(shí)執(zhí)行java -jar springbootstarterdemo-0.0.1-SNAPSHOT.jar就能啟動(dòng)應(yīng)用,如下圖:


接下來(lái)就用這個(gè)springbootstarterdemo-0.0.1-SNAPSHOT.jar來(lái)分析jar文件能夠獨(dú)立啟動(dòng)的原因;

java -jar做了什么

先要弄清楚java -jar命令做了什么,在oracle官網(wǎng)找到了該命令的描述:

If the -jar option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class manifest header in its source code.

再次秀出我蹩腳的英文翻譯:

  • 使用-jar參數(shù)時(shí),后面的參數(shù)是的jar文件名(本例中是springbootstarterdemo-0.0.1-SNAPSHOT.jar);
  • 該jar文件中包含的是class和資源文件;
  • 在manifest文件中有Main-Class的定義;
  • Main-Class的源碼中指定了整個(gè)應(yīng)用的啟動(dòng)類;(in its source code)

小結(jié)一下:
java -jar會(huì)去找jar中的manifest文件,在那里面找到真正的啟動(dòng)類;

探查springbootstarterdemo-0.0.1-SNAPSHOT.jarspringbootstarterdemo-0.0.1-SNAPSHOT.jar是前面的springboot工程的構(gòu)建結(jié)果,是個(gè)壓縮包,用常見(jiàn)的壓縮工具就能解壓,我這里的環(huán)境是MacBook Pro,用unzip即可解壓;解壓后有很多內(nèi)容,我們先關(guān)注manifest相關(guān)的,下圖紅框中就是manifest文件:

打開(kāi)上圖紅框中的文件,內(nèi)容如下:

Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: springbootstarterdemo
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter
 demoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.3.1.RELEASE
Created-By: Maven Jar Plugin 3.2.0
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher

4.在上述內(nèi)容可見(jiàn)Main-Class的值org.springframework.boot.loader.JarLauncher,這個(gè)和前面的java官方文檔對(duì)應(yīng)上了,正是這個(gè)JarLauncher類的代碼中指定了真正的啟動(dòng)類;

疑惑出現(xiàn)

1.在MANIFEST.MF文件中有這么一行內(nèi)容:

Start-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter
 demoApplication

2.前面的java官方文檔中,只提到過(guò)Main-Class ,并沒(méi)有提到Start-Class;

3.Start-Class的值是SpringbootstarterdemoApplication,這是我們的java代碼中的唯一類,也只真正的應(yīng)用啟動(dòng)類;

4.所以問(wèn)題就來(lái)了:理論上看,執(zhí)行java -jar命令時(shí)JarLauncher類會(huì)被執(zhí)行,但實(shí)際上是SpringbootstarterdemoApplication被執(zhí)行了,這其中發(fā)生了什么呢?

猜測(cè)

動(dòng)手之前先猜一下,個(gè)人覺(jué)得原因應(yīng)該如下:

  1. java -jar命令會(huì)啟動(dòng)JarLauncher;
  2. Start-Class是給JarLauncher用的;
  3. JarLauncher根據(jù)Start-Class找到了SpringbootstarterdemoApplication,然后執(zhí)行它;

分析JarLauncher先下載SpringBoot源碼,我下載的是2.3.1版本,地址:https://github.com/spring-projects/spring-boot/releases/tag/v2.3.1.RELEASEJarLauncher所在的工程是spring-boot-loader,先弄明白JarLauncher的繼承關(guān)系,如下圖,可見(jiàn)JarLauncher繼承自ExecutableArchiveLauncher,而ExecutableArchiveLauncher的父類Launcher位于最頂層,是個(gè)抽象類:

3. java -jar執(zhí)行的是JarLauncher的main方法,如下,會(huì)實(shí)例化一個(gè)JarLauncher對(duì)象,然后執(zhí)行其launch方法,并且將所有入?yún)⒍紟耄?/p>

public static void main(String[] args) throws Exception {
	new JarLauncher().launch(args);
}

4.上面的launch方法在父類Launcher中:

protected void launch(String[] args) throws Exception {
  // 將jar解壓后運(yùn)行的方式叫做exploded mode
  // 如果是exploded mode,就不能支持通過(guò)URL加載jar
  // 如果不是exploded mode,就可以通過(guò)URL加載jar
	if (!isExploded()) {
	  // 如果允許通過(guò)URL加載jar,就在此注冊(cè)對(duì)應(yīng)的處理類
		JarFile.registerUrlProtocolHandler();
	}
	// 創(chuàng)建classLoader
	ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
	// jarmode是創(chuàng)建docker鏡像時(shí)用到的參數(shù),使用該參數(shù)是為了生成帶有多個(gè)layer信息的鏡像
	// 這里暫時(shí)不關(guān)注jarmode
	String jarMode = System.getProperty("jarmode");
	//如果沒(méi)有jarmode參數(shù),launchClass的值就來(lái)自getMainClass()返回
	String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
	launch(args, launchClass, classLoader);
}

5.可見(jiàn)要重點(diǎn)關(guān)注的是getMainClass()方法,在看這個(gè)方法之前,我們先去關(guān)注一個(gè)重要的成員變量archive,是JarLauncher的父類ExecutableArchiveLauncher的archive,如下可見(jiàn),該變量又來(lái)自方法createArchive:

public ExecutableArchiveLauncher() {
		try {
			this.archive = createArchive();
			this.classPathIndex = getClassPathIndex(this.archive);
		}
		catch (Exception ex) {
			throw new IllegalStateException(ex);
		}
	}

6.方法來(lái)自Launcher.createArchive,如下所示,可見(jiàn)成員變量archive實(shí)際上是個(gè)JarFileArchive對(duì)象:

protected final Archive createArchive() throws Exception {
		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);
		}
		return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
	}

7.現(xiàn)在回到getMainClass()方法,可見(jiàn)this.archive.getManifest方法返回的是META-INF/MANIFEST.MF文件的內(nèi)容,然后getValue(START_CLASS_ATTRIBUTE)方法實(shí)際上就是從META-INF/MANIFEST.MF中取得了Start-Class的屬性:

@Override
	protected String getMainClass() throws Exception {
	  // 對(duì)應(yīng)的是JarFileArchive.getManifest方法,
	  // 進(jìn)去后發(fā)現(xiàn)對(duì)應(yīng)的就是JarFile.getManifest方法,
	  // JarFile.getManifest對(duì)應(yīng)的就是META-INF/MANIFEST.MF文件的內(nèi)容
		Manifest manifest = this.archive.getManifest();
		String mainClass = null;
		if (manifest != null) {
		  // 對(duì)應(yīng)的是META-INF/MANIFEST.MF文件中的Start-Class的屬性
			mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
		}
		if (mainClass == null) {
			throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
		}
		return mainClass;
	}

8.從上述分析可知:getMainClass()方法返回的是META-INF/MANIFEST.MF中取得了Start-Class的屬性com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication,再次回到launch方法中,可見(jiàn)最終運(yùn)行的代碼是launch(args, launchClass, classLoader),它的launchClass參數(shù)就是com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication:

protected void launch(String[] args) throws Exception {
		if (!isExploded()) {
			JarFile.registerUrlProtocolHandler();
		}
		ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
		String jarMode = System.getProperty("jarmode");
		// 這里的launchClass等于"com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication"
		String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
		// 這里就是啟動(dòng)SpringbootstarterdemoApplication的地方
		launch(args, launchClass, classLoader);
	}

9.展開(kāi)launch(args, launchClass, classLoader),最終查到了MainMethodRunner類:

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) {
	  // mainClassName被賦值為"com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication"
		this.mainClassName = mainClass;
		this.args = (args != null) ? args.clone() : null;
	}

	public void run() throws Exception {
	  // 得到SpringbootstarterdemoApplication的Class對(duì)象
		Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
		// 得到SpringbootstarterdemoApplication的main方法對(duì)象
		Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
		mainMethod.setAccessible(true);
		// 通過(guò)反射執(zhí)行main方法
		mainMethod.invoke(null, new Object[] { this.args });
	}
}

終于,真相大白了;

小結(jié)

最后盡可能簡(jiǎn)短做個(gè)小結(jié),先看jar是如何產(chǎn)生的,如下圖,maven插件生成的jar文件中,有常見(jiàn)的class、jar,也有符合java規(guī)范的MANIFEST.MF文件,并且,還在MANIFEST.MF文件中額外生成了名為Start-Class的配置,這里面是我們編寫的應(yīng)用啟動(dòng)類SpringbootstarterdemoApplication:

啟動(dòng)類是JarLauncher,它是如何與MANIFEST.MF文件關(guān)聯(lián)的呢?從下圖可以看出,最終是通過(guò)JarFile類的成員變量manifestSupplier關(guān)聯(lián)上的:

再來(lái)看看關(guān)鍵代碼的執(zhí)行情況,如下圖:

至此,SpringBoot的jar獨(dú)立運(yùn)行的基本原理已經(jīng)清楚,探究的過(guò)程中,除了熟悉關(guān)鍵代碼流程,還對(duì)jar中的文件有了更多了解,如果您正在學(xué)習(xí)SpringBoot,希望本文能給您一些參考;

官方文檔最后附上SpringBoot官方文檔,可以看到Start-Class描述信息:

上述文檔明確提到:Start-Class定義的是實(shí)際的啟動(dòng)類,此時(shí)的您應(yīng)該對(duì)一切都了然于胸,產(chǎn)生本該如此的感慨;

到此這篇關(guān)于springboot的jar為何能獨(dú)立運(yùn)行的文章就介紹到這了,更多相關(guān)springboot的jar為何能獨(dú)立運(yùn)行內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot項(xiàng)目 文件上傳臨時(shí)目標(biāo)被刪除異常的處理方案

    SpringBoot項(xiàng)目 文件上傳臨時(shí)目標(biāo)被刪除異常的處理方案

    這篇文章主要介紹了SpringBoot項(xiàng)目 文件上傳臨時(shí)目標(biāo)被刪除異常的處理方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Java中Lambda表達(dá)式用法介紹

    Java中Lambda表達(dá)式用法介紹

    本文詳細(xì)講解了Java中Lambda表達(dá)式的用法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-12-12
  • Java調(diào)用WebService服務(wù)的三種方式總結(jié)

    Java調(diào)用WebService服務(wù)的三種方式總結(jié)

    雖然WebService這個(gè)框架已經(jīng)過(guò)時(shí),但是有些公司還在使用,在調(diào)用他們的服務(wù)的時(shí)候就不得不面對(duì)各種問(wèn)題,本篇文章總結(jié)了最近我調(diào)用?WebService的心路歷程,3種方式可以分別嘗試,需要的朋友可以參考下
    2023-08-08
  • 深入理解java內(nèi)置鎖(synchronized)和顯式鎖(ReentrantLock)

    深入理解java內(nèi)置鎖(synchronized)和顯式鎖(ReentrantLock)

    這篇文章主要介紹了Java多線程之內(nèi)置鎖(synchronized)和顯式鎖(ReentrantLock)的深入理解新的和用法,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-11-11
  • Docker 部署 SpringBoot 項(xiàng)目整合 Redis 鏡像做訪問(wèn)計(jì)數(shù)示例代碼

    Docker 部署 SpringBoot 項(xiàng)目整合 Redis 鏡像做訪問(wèn)計(jì)數(shù)示例代碼

    這篇文章主要介紹了Docker 部署 SpringBoot 項(xiàng)目整合 Redis 鏡像做訪問(wèn)計(jì)數(shù)Demo,本文給大家介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • Java中Collections.emptyList()的注意事項(xiàng)

    Java中Collections.emptyList()的注意事項(xiàng)

    這篇文章主要給大家介紹了關(guān)于Java中Collections.emptyList()的注意事項(xiàng),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • Tomcat數(shù)據(jù)源配置方法_JBuilder中

    Tomcat數(shù)據(jù)源配置方法_JBuilder中

    今天幫一同事配置一個(gè)數(shù)據(jù)源,采用tomcat5.5.9,本來(lái)是個(gè)很簡(jiǎn)單的事,以前也配過(guò),但由于很長(zhǎng)時(shí)間沒(méi)用過(guò)容器提供的數(shù)據(jù)源了(IOC用慣了),也只記的個(gè)大概了,所以剛開(kāi)始一配就出錯(cuò)了,google了一下,有很多資料,照著試試卻都不好使(到不是別人說(shuō)的不對(duì),只是大家用的版本不同)。
    2008-10-10
  • Java BeanUtils.copyProperties的詳解

    Java BeanUtils.copyProperties的詳解

    這篇文章主要介紹了Java BeanUtils.copyProperties的詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • Java面向?qū)ο笾橄箢?接口的那些事

    Java面向?qū)ο笾橄箢?接口的那些事

    這篇文章主要介紹了Java面向?qū)ο蠡A(chǔ):抽象類和接口,文中代碼可以幫助各位更好的理解學(xué)習(xí),有需求的小伙伴可以參考下,希望能夠給你帶來(lái)幫助
    2021-08-08
  • 使用Springboot整合GridFS實(shí)現(xiàn)文件操作

    使用Springboot整合GridFS實(shí)現(xiàn)文件操作

    這篇文章主要介紹了使用Springboot整合GridFS實(shí)現(xiàn)文件操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10

最新評(píng)論