真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

SpringBoot中jar可執(zhí)行的原理是什么

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)Spring Boot中jar可執(zhí)行的原理是什么,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

沁水網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)建站!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、APP開(kāi)發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)公司等網(wǎng)站項(xiàng)目制作,到程序開(kāi)發(fā),運(yùn)營(yíng)維護(hù)。創(chuàng)新互聯(lián)建站自2013年起到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來(lái)保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)建站

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)的類(lèi)。

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

也就是說(shuō)想要知道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ù)。


  org.springframework.boot
  spring-boot-maven-plugin
  
    
      
        repackage
      
    
  

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());
  //查找并過(guò)濾項(xiàng)目運(yùn)行時(shí)依賴的jar
  Set 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方法的類(lèi),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)類(lèi)。

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)類(lèi)介紹

  • java.util.jar.JarFile JDK工具類(lèi)提供的讀取jar文件

  • org.springframework.boot.loader.jar.JarFileSpringboot-loader 繼承JDK提供JarFile類(lèi)

  • java.util.jar.JarEntryDK工具類(lèi)提供的``jar```文件條目

  • org.springframework.boot.loader.jar.JarEntry Springboot-loader 繼承JDK提供JarEntry類(lèi)

  • org.springframework.boot.loader.archive.Archive Springboot抽象出來(lái)的統(tǒng)一訪問(wèn)資源的層

    • JarFileArchivejar包文件的抽象

    • ExplodedArchive文件目錄

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

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

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

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

  •   最基礎(chǔ):Bootstrap ClassLoader(加載JDK的/lib目錄下的類(lèi))

  •   次基礎(chǔ):Extension ClassLoader(加載JDK的/lib/ext目錄下的類(lèi))

  •   普通:Application ClassLoader(程序自己classpath下的類(lèi))

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

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

在上面我們講到Spring boot支持多個(gè)'!/‘以表示多個(gè)jar,而我們的問(wèn)題在于,如何解決查找到這多個(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包依賴的所有類(lèi)和資源,將該urls參數(shù)傳遞給父類(lèi)java.net.URLClassLoader,由父類(lèi)的java.net.URLClassLoader#findClass執(zhí)行查找類(lèi)方法,該類(lèi)的查找來(lái)源即構(gòu)造方法傳遞進(jìn)來(lái)的urls參數(shù)

//LaunchedURLClassLoader的實(shí)現(xiàn)
protected Class loadClass(String name, boolean resolve)
   throws ClassNotFoundException {
  Handler.setUseFastConnectionExceptions(true);
  try {
   try {
     //嘗試根據(jù)類(lèi)名去定義類(lèi)所在的包,即java.lang.Package,確保jar in jar里匹配的manifest能夠和關(guān)聯(lián)	      //的package關(guān)聯(lián)起來(lái)
     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ù)先過(guò)濾掉查找不到的包
      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)行查找類(lèi),而B(niǎo)ootstrap ClassLoader和Extension ClassLoader將會(huì)查找不到fat jar依賴的類(lèi),最終會(huì)來(lái)到Application ClassLoader,調(diào)用java.net.URLClassLoader#findClass

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

Springboot2和Springboot1的最大區(qū)別在于,Springboo1會(huì)新起一個(gè)線程,來(lái)執(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)前線程的上下文類(lèi)加載器設(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類(lèi)加載器,這里使用了2個(gè)URL,分別對(duì)應(yīng)jar包中依賴包spring-boot-loader和spring-boot,使用 "!/" 分開(kāi),需要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());
// 加載類(lèi)
// 這2個(gè)類(lèi)都會(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);
  }

    
      org.springframework.boot
      spring-boot-loader
      2.1.3.RELEASE
    
    
      org.springframework.boot
      spring-boot-maven-plugin
      2.1.3.RELEASE

    

上述就是小編為大家分享的Spring Boot中jar可執(zhí)行的原理是什么了,如果剛好有類(lèi)似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


網(wǎng)站欄目:SpringBoot中jar可執(zhí)行的原理是什么
網(wǎng)頁(yè)鏈接:http://weahome.cn/article/igphjd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部