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

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

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

前言

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

spring-boot-maven-plugin

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

fat jar 目錄結構
├─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工作機制,而spring-boot-maven-plugin屬于自定義插件,因此我們又必須知道,Maven的自定義插件是如何工作的

Maven的自定義插件

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

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

repackage目標對應的將執(zhí)行到org.springframework.boot.maven.RepackageMojo#execute,該方法的主要邏輯是調用了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());
  //查找并過濾項目運行時依賴的jar
  Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(),
     getFilters(getAdditionalFilters()));
  //將artifacts轉換成libraries
  Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack,
     getLog());
  try {
    //提供Spring Boot啟動腳本
   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());
}

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

private Repackager getRepackager(File source) {
  Repackager repackager = new Repackager(source, this.layoutFactory);
  repackager.addMainClassTimeoutWarningListener(
     new LoggingMainClassTimeoutWarningListener());
  //設置main class的名稱,如果不指定的話則會查找第一個包含main方法的類,repacke最后將會設置org.springframework.boot.loader.JarLauncher
  repackager.setMainClass(this.mainClass);
  if (this.layout != null) {
   getLog().info("Layout: " + this.layout);
    //重點關心下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我們可以將之翻譯為文件布局,或者目錄布局,代碼一看清晰明了,同時我們需要關注,也是下一個重點關注對象org.springframework.boot.loader.JarLauncher,從名字推斷,這很可能是返回可執(zhí)行jar文件的啟動類。

MANIFEST.MF文件內容

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文件為以上信息,可以看到兩個關鍵信息Main-Class和Start-Class。我們可以進一步,程序的啟動入口并不是我們SpringBoot中定義的main,而是JarLauncher#main,而再在其中利用反射調用定義好的Start-Class的main方法

JarLauncher

重點類介紹

  • 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文件目錄

這里重點描述一下JarFile的作用,每個JarFileArchive都會對應一個JarFile。在構造的時候會解析內部結構,去獲取jar包里的各個文件或文件夾類。我們可以看一下該類的注釋。

/* 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只支持一個'!/‘,而Spring boot擴展了這個協(xié)議,讓它支持多個'!/‘,就可以表示jar in jar、jar in directory、fat jar的資源了。

自定義類加載機制

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

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

先簡單的介紹Springboot2中LaunchedURLClassLoader,該類繼承了java.net.URLClassLoader,重寫了java.lang.ClassLoader#loadClass(java.lang.String, boolean),然后我們再探討他是如何修改雙親委派機制。

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

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í)行查找類方法,該類的查找來源即構造方法傳遞進來的urls參數(shù)

//LaunchedURLClassLoader的實現(xiàn)
protected Class<?> loadClass(String name, boolean resolve)
   throws ClassNotFoundException {
  Handler.setUseFastConnectionExceptions(true);
  try {
   try {
     //嘗試根據(jù)類名去定義類所在的包,即java.lang.Package,確保jar in jar里匹配的manifest能夠和關聯(lián)	      //的package關聯(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方法的作用實際上是預先過濾掉查找不到的包
      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)實際上會回到了java.lang.ClassLoader#loadClass(java.lang.String, boolean),遵循雙親委派機制進行查找類,而Bootstrap ClassLoader和Extension ClassLoader將會查找不到fat jar依賴的類,最終會來到Application ClassLoader,調用java.net.URLClassLoader#findClass

如何真正的啟動

Springboot2和Springboot1的最大區(qū)別在于,Springboo1會新起一個線程,來執(zhí)行相應的反射調用邏輯,而SpringBoot2則去掉了構建新的線程這一步。方法是org.springframework.boot.loader.Launcher#launch(java.lang.String[], java.lang.String, java.lang.ClassLoader)反射調用邏輯比較簡單,這里就不再分析,比較關鍵的一點是,在調用main方法之前,將當前線程的上下文類加載器設置成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();
// 構造LaunchedURLClassLoader類加載器,這里使用了2個URL,分別對應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個類都會在第二步本地查找中被找出(URLClassLoader的findClass方法)
    classLoader.loadClass("org.springframework.boot.loader.JarLauncher");
    classLoader.loadClass("org.springframework.boot.SpringApplication");
// 在第三步使用默認的加載順序在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>

總結

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

好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。

相關文章

  • Spring Boot 如何自定義返回錯誤碼錯誤信息

    Spring Boot 如何自定義返回錯誤碼錯誤信息

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

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

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

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

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

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

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

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

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

    Java算法之數(shù)組冒泡排序代碼實例講解

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

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

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

    Spring如何使用通知?Advice?管理事務

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

    Java構造器方法深入理解

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

    Java復制文件常用的三種方法

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

最新評論

吕梁市| 陇川县| 东光县| 大埔县| 铁岭市| 石首市| 潮州市| 清原| 冷水江市| 个旧市| 航空| 民勤县| 开原市| 开鲁县| 读书| 汾西县| 宜昌市| 临猗县| 当雄县| 仁布县| 汉阴县| 彝良县| 昌江| 盐边县| 沂源县| 巴青县| 杂多县| 陆丰市| 米易县| 武川县| 即墨市| 阳西县| 余江县| 石嘴山市| 舞阳县| 清远市| 龙江县| 尚义县| 乌兰察布市| 台东县| 新邵县|