最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Boot jar可執(zhí)行原理的徹底分析

 更新時(shí)間:2019年07月26日 08:34:45   作者:plz叫我紅領(lǐng)巾  
這篇文章主要給大家介紹了關(guān)于Spring Boot jar可執(zhí)行原理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧

前言

文章篇幅較長(zhǎng),但是包含了SpringBoot 可執(zhí)行jar包從頭到尾的原理,請(qǐng)讀者耐心觀看。同時(shí)文章是基于SpringBoot-2.1.3進(jìn)行分析。涉及的知識(shí)點(diǎn)主要包括Maven的生命周期以及自定義插件,JDK提供關(guān)于jar包的工具類以及Springboot如何擴(kuò)展,最后是自定義類加載器。

spring-boot-maven-plugin

SpringBoot 的可執(zhí)行jar包又稱fat jar ,是包含所有第三方依賴的 jar 包,jar 包中嵌入了除 java 虛擬機(jī)以外的所有依賴,是一個(gè) all-in-one jar 包。普通插件maven-jar-plugin生成的包和spring-boot-maven-plugin生成的包之間的直接區(qū)別,是fat jar中主要增加了兩部分,第一部分是lib目錄,存放的是Maven依賴的jar包文件,第二部分是spring boot loader相關(guān)的類。

fat jar 目錄結(jié)構(gòu)
├─BOOT-INF
│  ├─classes
│  └─lib
├─META-INF
│  ├─maven
│  ├─app.properties
│  ├─MANIFEST.MF     
└─org
    └─springframework
        └─boot
            └─loader
                ├─archive
                ├─data
                ├─jar
                └─util

也就是說想要知道fat jar是如何生成的,就必須知道spring-boot-maven-plugin工作機(jī)制,而spring-boot-maven-plugin屬于自定義插件,因此我們又必須知道,Maven的自定義插件是如何工作的

Maven的自定義插件

Maven 擁有三套相互獨(dú)立的生命周期: clean、default 和 site, 而每個(gè)生命周期包含一些phase階段, 階段是有順序的, 并且后面的階段依賴于前面的階段。生命周期的階段phase與插件的目標(biāo)goal相互綁定,用以完成實(shí)際的構(gòu)建任務(wù)。

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <executions>
    <execution>
      <goals>
        <goal>repackage</goal>
      </goals>
    </execution>
  </executions>
</plugin>

repackage目標(biāo)對(duì)應(yīng)的將執(zhí)行到org.springframework.boot.maven.RepackageMojo#execute,該方法的主要邏輯是調(diào)用了org.springframework.boot.maven.RepackageMojo#repackage

private void repackage() throws MojoExecutionException {
   //獲取使用maven-jar-plugin生成的jar,最終的命名將加上.orignal后綴
  Artifact source = getSourceArtifact();
  //最終文件,即Fat jar
  File target = getTargetFile();
  //獲取重新打包器,將重新打包成可執(zhí)行jar文件
  Repackager repackager = getRepackager(source.getFile());
  //查找并過濾項(xiàng)目運(yùn)行時(shí)依賴的jar
  Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(),
     getFilters(getAdditionalFilters()));
  //將artifacts轉(zhuǎn)換成libraries
  Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack,
     getLog());
  try {
    //提供Spring Boot啟動(dòng)腳本
   LaunchScript launchScript = getLaunchScript();
    //執(zhí)行重新打包邏輯,生成最后fat jar
   repackager.repackage(target, libraries, launchScript);
  }
  catch (IOException ex) {
   throw new MojoExecutionException(ex.getMessage(), ex);
  }
  //將source更新成 xxx.jar.orignal文件
  updateArtifact(source, target, repackager.getBackupFile());
}

我們關(guān)心一下org.springframework.boot.maven.RepackageMojo#getRepackager這個(gè)方法,知道Repackager是如何生成的,也就大致能夠推測(cè)出內(nèi)在的打包邏輯。

private Repackager getRepackager(File source) {
  Repackager repackager = new Repackager(source, this.layoutFactory);
  repackager.addMainClassTimeoutWarningListener(
     new LoggingMainClassTimeoutWarningListener());
  //設(shè)置main class的名稱,如果不指定的話則會(huì)查找第一個(gè)包含main方法的類,repacke最后將會(huì)設(shè)置org.springframework.boot.loader.JarLauncher
  repackager.setMainClass(this.mainClass);
  if (this.layout != null) {
   getLog().info("Layout: " + this.layout);
    //重點(diǎn)關(guān)心下layout 最終返回了 org.springframework.boot.loader.tools.Layouts.Jar
   repackager.setLayout(this.layout.layout());
  }
  return repackager;
}
/**
 * Executable JAR layout.
 */
public static class Jar implements RepackagingLayout {
  @Override
  public String getLauncherClassName() {
   return "org.springframework.boot.loader.JarLauncher";
  }
  @Override
  public String getLibraryDestination(String libraryName, LibraryScope scope) {
   return "BOOT-INF/lib/";
  }
  @Override
  public String getClassesLocation() {
   return "";
  }
  @Override
  public String getRepackagedClassesLocation() {
   return "BOOT-INF/classes/";
  }
  @Override
  public boolean isExecutable() {
   return true;
  }
}

layout我們可以將之翻譯為文件布局,或者目錄布局,代碼一看清晰明了,同時(shí)我們需要關(guān)注,也是下一個(gè)重點(diǎn)關(guān)注對(duì)象org.springframework.boot.loader.JarLauncher,從名字推斷,這很可能是返回可執(zhí)行jar文件的啟動(dòng)類。

MANIFEST.MF文件內(nèi)容

Manifest-Version: 1.0
Implementation-Title: oneday-auth-server
Implementation-Version: 1.0.0-SNAPSHOT
Archiver-Version: Plexus Archiver
Built-By: oneday
Implementation-Vendor-Id: com.oneday
Spring-Boot-Version: 2.1.3.RELEASE
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.oneday.auth.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_171

repackager生成的MANIFEST.MF文件為以上信息,可以看到兩個(gè)關(guān)鍵信息Main-Class和Start-Class。我們可以進(jìn)一步,程序的啟動(dòng)入口并不是我們SpringBoot中定義的main,而是JarLauncher#main,而再在其中利用反射調(diào)用定義好的Start-Class的main方法

JarLauncher

重點(diǎn)類介紹

  • java.util.jar.JarFile JDK工具類提供的讀取jar文件
  • org.springframework.boot.loader.jar.JarFileSpringboot-loader 繼承JDK提供JarFile類
  • java.util.jar.JarEntryDK工具類提供的``jar```文件條目
  • org.springframework.boot.loader.jar.JarEntry Springboot-loader 繼承JDK提供JarEntry類
  • org.springframework.boot.loader.archive.Archive Springboot抽象出來的統(tǒng)一訪問資源的層
    • JarFileArchivejar包文件的抽象
    • ExplodedArchive文件目錄

這里重點(diǎn)描述一下JarFile的作用,每個(gè)JarFileArchive都會(huì)對(duì)應(yīng)一個(gè)JarFile。在構(gòu)造的時(shí)候會(huì)解析內(nèi)部結(jié)構(gòu),去獲取jar包里的各個(gè)文件或文件夾類。我們可以看一下該類的注釋。

/* Extended variant of {@link java.util.jar.JarFile} that behaves in the same way but
* offers the following additional functionality.
* <ul>
* <li>A nested {@link JarFile} can be {@link #getNestedJarFile(ZipEntry) obtained} based
* on any directory entry.</li>
* <li>A nested {@link JarFile} can be {@link #getNestedJarFile(ZipEntry) obtained} for
* embedded JAR files (as long as their entry is not compressed).</li>
**/ </ul>

jar里的資源分隔符是!/,在JDK提供的JarFile URL只支持一個(gè)'!/‘,而Spring boot擴(kuò)展了這個(gè)協(xié)議,讓它支持多個(gè)'!/‘,就可以表示jar in jar、jar in directory、fat jar的資源了。

自定義類加載機(jī)制

  • ​  最基礎(chǔ):Bootstrap ClassLoader(加載JDK的/lib目錄下的類)
  • ​  次基礎(chǔ):Extension ClassLoader(加載JDK的/lib/ext目錄下的類)
  • ​  普通:Application ClassLoader(程序自己classpath下的類)

首先需要關(guān)注雙親委派機(jī)制很重要的一點(diǎn)是,如果一個(gè)類可以被委派最基礎(chǔ)的ClassLoader加載,就不能讓高層的ClassLoader加載,這樣是為了范圍錯(cuò)誤的引入了非JDK下但是類名一樣的類。其二,如果在這個(gè)機(jī)制下,由于fat jar中依賴的各個(gè)第三方j(luò)ar文件,并不在程序自己classpath下,也就是說,如果我們采用雙親委派機(jī)制的話,根本獲取不到我們所依賴的jar包,因此我們需要修改雙親委派機(jī)制的查找class的方法,自定義類加載機(jī)制。

先簡(jiǎn)單的介紹Springboot2中LaunchedURLClassLoader,該類繼承了java.net.URLClassLoader,重寫了java.lang.ClassLoader#loadClass(java.lang.String, boolean),然后我們?cè)偬接懰侨绾涡薷碾p親委派機(jī)制。

在上面我們講到Spring boot支持多個(gè)'!/‘以表示多個(gè)jar,而我們的問題在于,如何解決查找到這多個(gè)jar包。我們看一下LaunchedURLClassLoader的構(gòu)造方法。

public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
  super(urls, parent);
}

urls注釋解釋道the URLs from which to load classes and resources,即fat jar包依賴的所有類和資源,將該urls參數(shù)傳遞給父類java.net.URLClassLoader,由父類的java.net.URLClassLoader#findClass執(zhí)行查找類方法,該類的查找來源即構(gòu)造方法傳遞進(jìn)來的urls參數(shù)

//LaunchedURLClassLoader的實(shí)現(xiàn)
protected Class<?> loadClass(String name, boolean resolve)
   throws ClassNotFoundException {
  Handler.setUseFastConnectionExceptions(true);
  try {
   try {
     //嘗試根據(jù)類名去定義類所在的包,即java.lang.Package,確保jar in jar里匹配的manifest能夠和關(guān)聯(lián)	      //的package關(guān)聯(lián)起來
     definePackageIfNecessary(name);
   }
   catch (IllegalArgumentException ex) {
     // Tolerate race condition due to being parallel capable
     if (getPackage(name) == null) {
      // This should never happen as the IllegalArgumentException indicates
      // that the package has already been defined and, therefore,
      // getPackage(name) should not return null.
       
      //這里異常表明,definePackageIfNecessary方法的作用實(shí)際上是預(yù)先過濾掉查找不到的包
      throw new AssertionError("Package " + name + " has already been "
         + "defined but it could not be found");
     }
   }
   return super.loadClass(name, resolve);
  }
  finally {
   Handler.setUseFastConnectionExceptions(false);
  }
}

方法super.loadClass(name, resolve)實(shí)際上會(huì)回到了java.lang.ClassLoader#loadClass(java.lang.String, boolean),遵循雙親委派機(jī)制進(jìn)行查找類,而Bootstrap ClassLoader和Extension ClassLoader將會(huì)查找不到fat jar依賴的類,最終會(huì)來到Application ClassLoader,調(diào)用java.net.URLClassLoader#findClass

如何真正的啟動(dòng)

Springboot2和Springboot1的最大區(qū)別在于,Springboo1會(huì)新起一個(gè)線程,來執(zhí)行相應(yīng)的反射調(diào)用邏輯,而SpringBoot2則去掉了構(gòu)建新的線程這一步。方法是org.springframework.boot.loader.Launcher#launch(java.lang.String[], java.lang.String, java.lang.ClassLoader)反射調(diào)用邏輯比較簡(jiǎn)單,這里就不再分析,比較關(guān)鍵的一點(diǎn)是,在調(diào)用main方法之前,將當(dāng)前線程的上下文類加載器設(shè)置成LaunchedURLClassLoader

protected void launch(String[] args, String mainClass, ClassLoader classLoader)
   throws Exception {
  Thread.currentThread().setContextClassLoader(classLoader);
  createMainMethodRunner(mainClass, args, classLoader).run();
}

Demo

public static void main(String[] args) throws ClassNotFoundException, MalformedURLException {
    JarFile.registerUrlProtocolHandler();
// 構(gòu)造LaunchedURLClassLoader類加載器,這里使用了2個(gè)URL,分別對(duì)應(yīng)jar包中依賴包spring-boot-loader和spring-boot,使用 "!/" 分開,需要org.springframework.boot.loader.jar.Handler處理器處理
    LaunchedURLClassLoader classLoader = new LaunchedURLClassLoader(
        new URL[] {
            new URL("jar:file:/E:/IdeaProjects/oneday-auth/oneday-auth-server/target/oneday-auth-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-loader-1.2.3.RELEASE.jar!/")
            , new URL("jar:file:/E:/IdeaProjects/oneday-auth/oneday-auth-server/target/oneday-auth-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-2.1.3.RELEASE.jar!/")
        },
        Application.class.getClassLoader());
// 加載類
// 這2個(gè)類都會(huì)在第二步本地查找中被找出(URLClassLoader的findClass方法)
    classLoader.loadClass("org.springframework.boot.loader.JarLauncher");
    classLoader.loadClass("org.springframework.boot.SpringApplication");
// 在第三步使用默認(rèn)的加載順序在ApplicationClassLoader中被找出
  classLoader.loadClass("org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration");

//    SpringApplication.run(Application.class, args);
  }
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-loader -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-loader</artifactId>
      <version>2.1.3.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <version>2.1.3.RELEASE</version>

    </dependency>

總結(jié)

對(duì)于源碼分析,這次的較大收獲則是不能一下子去追求弄懂源碼中的每一步代碼的邏輯,即便我知道該方法的作用。我們需要搞懂的是關(guān)鍵代碼,以及涉及到的知識(shí)點(diǎn)。我從Maven的自定義插件開始進(jìn)行追蹤,鞏固了對(duì)Maven的知識(shí)點(diǎn),在這個(gè)過程中甚至了解到JDK對(duì)jar的讀取是有提供對(duì)應(yīng)的工具類。最后最重要的知識(shí)點(diǎn)則是自定義類加載器。整個(gè)代碼下來并不是說代碼究竟有多優(yōu)秀,而是要學(xué)習(xí)他因何而優(yōu)秀。

好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • Spring Boot 如何自定義返回錯(cuò)誤碼錯(cuò)誤信息

    Spring Boot 如何自定義返回錯(cuò)誤碼錯(cuò)誤信息

    這篇文章主要介紹了Spring Boot 如何自定義返回錯(cuò)誤碼錯(cuò)誤信息的相關(guān)知識(shí),非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-08-08
  • Java文件過濾器實(shí)現(xiàn)按條件篩選文件

    Java文件過濾器實(shí)現(xiàn)按條件篩選文件

    本文主要介紹了Java文件過濾器實(shí)現(xiàn)按條件篩選文件,文件過濾器是在文件處理中起到重要作用的工具,它可以用來篩選文件并根據(jù)特定的條件進(jìn)行過濾,下面就來介紹一下
    2024-04-04
  • Spring Boot中如何使用Convert接口實(shí)現(xiàn)類型轉(zhuǎn)換器

    Spring Boot中如何使用Convert接口實(shí)現(xiàn)類型轉(zhuǎn)換器

    這篇文章主要介紹了Spring Boot中使用Convert接口實(shí)現(xiàn)類型轉(zhuǎn)換器的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Spring Boot 集成 RocketMQ 全流程指南(從依賴引入到消息收發(fā))

    Spring Boot 集成 RocketMQ 全流程指南(從依賴引入到消息收發(fā)

    本文將通過 手動(dòng)連接 和 配置連接 兩種方式,詳細(xì)講解如何在 Spring Boot 中集成 RocketMQ,實(shí)現(xiàn)消息的同步與異步發(fā)送,并提供完整示例代碼,感興趣的朋友一起看看吧
    2025-04-04
  • Java日常開發(fā)中讀寫TXT文本舉例詳解

    Java日常開發(fā)中讀寫TXT文本舉例詳解

    這篇文章主要給大家介紹了關(guān)于Java日常開發(fā)中讀寫TXT文本,包括使用BufferedReader、Scanner、FileInputStream等類進(jìn)行讀取,以及使用BufferedWriter、PrintWriter、FileOutputStream等類進(jìn)行寫入,需要的朋友可以參考下
    2024-12-12
  • Java算法之?dāng)?shù)組冒泡排序代碼實(shí)例講解

    Java算法之?dāng)?shù)組冒泡排序代碼實(shí)例講解

    這篇文章主要介紹了Java算法之?dāng)?shù)組冒泡排序代碼實(shí)例講解,文中用代碼舉例講解的很清晰,有感興趣的同學(xué)可以研究下
    2021-03-03
  • Idea 解決 Could not autowire. No beans of ''xxxx'' type found 的錯(cuò)誤提示

    Idea 解決 Could not autowire. No beans of ''xxxx'' type found

    這篇文章主要介紹了Idea 解決 Could not autowire. No beans of 'xxxx' type found 的錯(cuò)誤提示,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2019-01-01
  • Spring如何使用通知?Advice?管理事務(wù)

    Spring如何使用通知?Advice?管理事務(wù)

    Spring 默認(rèn)采用聲明式事務(wù)管理(通過配置的方式) ,也可以實(shí)現(xiàn)編程式事務(wù)管理,這篇文章主要介紹了Spring使用通知Advice管理事務(wù),需要的朋友可以參考下
    2023-06-06
  • Java構(gòu)造器方法深入理解

    Java構(gòu)造器方法深入理解

    這篇文章主要介紹了Java構(gòu)造器方法深入理解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09
  • Java復(fù)制文件常用的三種方法

    Java復(fù)制文件常用的三種方法

    今天小編就為大家分享一篇關(guān)于Java復(fù)制文件常用的三種方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-03-03

最新評(píng)論

盐津县| 射洪县| 扎鲁特旗| 崇信县| 周至县| 平泉县| 梅河口市| 旬阳县| 辽阳市| 南昌县| 广东省| 和平县| 巧家县| 青海省| 津南区| 二手房| 晋州市| 南木林县| 涿州市| 军事| 昌江| 邛崃市| 大新县| 莱芜市| 福州市| 济南市| 交口县| 元谋县| 新邵县| 鹿泉市| 墨脱县| 三穗县| 莱州市| 册亨县| 鄂尔多斯市| 阿勒泰市| 桂阳县| 缙云县| 仁寿县| 鹿邑县| 陇南市|