新鮮出爐,直接帶你弄懂 Spring Boot Jar 啓動原理!

1. 概述

Spring Boot 提供了 Maven 插件 spring-boot-maven-plugin,可以方便的將 Spring Boot 項目打成 jar 包或者 war 包。

考慮到部署的便利性,我們絕大多數 99.99% 的場景下,我們會選擇打成 jar 包。這樣,我們就無需在部署項目的服務器上,配置相應的 Tomcat、Jetty 等 Servlet 容器。

那麼,jar 包是如何運行,並啓動 Spring Boot 項目的呢?這個就是本文的目的,一起弄懂 Spring Boot jar 包的運行原理

下面,我們來打開一個 Spring Boot jar 包,看看其裏面的結構。如下圖所示,一共分成四部分:

Spring Boot jar 包

  • ① META-INF 目錄:通過 MANIFEST.MF 文件提供 jar 包的元數據,聲明瞭 jar 的啓動類。

  • ② org 目錄:爲 Spring Boot 提供的 spring-boot-loader 項目,它是 java -jar 啓動 Spring Boot 項目的祕密所在,也是稍後我們將深入瞭解的部分。

    Spring Boot Loader provides the secret sauce that allows you to build a single jar file that can be launched using java -jar. Generally you will not need to use spring-boot-loader directly, but instead work with the Gradle or Maven plugin.

  • ③ BOOT-INF/lib 目錄:我們 Spring Boot 項目中引入的依賴的 jar 包們。spring-boot-loader 項目很大的一個作用,就是解決 jar 包裏嵌套 jar 的情況,如何加載到其中的類。

  • ④ BOOT-INF/classes 目錄:我們在 Spring Boot 項目中 Java 類所編譯的 .class、配置文件等等。

先簡單劇透下,spring-boot-loader 項目需要解決兩個問題:

  • 第一,如何引導執行我們創建的 Spring Boot 應用的啓動類,例如上述圖中的 Application 類。

  • 第二,如何加載 BOOT-INF/class 目錄下的類,以及 BOOT-INF/lib 目錄下內嵌的 jar 包中的類。

下面,尾隨艿艿,一起來抽絲剝繭!

2. MANIFEST.MF

我們來查看 META-INF/MANIFEST.MF 文件,裏面的內容如下:

Manifest-Version: 1.0
Implementation-Title: lab-39-demo
Implementation-Version: 2.2.2.RELEASE
Start-Class: cn.iocoder.springboot.lab39.skywalkingdemo.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.2.2.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher

它實際是一個 Properties 配置文件,每一行都是一個配置項目。重點來看看兩個配置項:

  • Main-Class 配置項:Java 規定的 jar 包的啓動類,這裏設置爲 spring-boot-loader 項目的 JarLauncher 類,進行 Spring Boot 應用的啓動。

  • Start-Class 配置項:Spring Boot 規定的啓動類,這裏設置爲我們定義的 Application 類。

小知識補充:爲什麼會有 Main-Class/Start-Class 配置項呢?因爲我們是通過 Spring Boot 提供的 Maven 插件 spring-boot-maven-plugin 進行打包,該插件將該配置項寫入到 MANIFEST.MF 中,從而能讓 spring-boot-loader 能夠引導啓動 Spring Boot 應用。

可能胖友會有疑惑,Start-Class 對應的 Application 類自帶了 #main(String[] args) 方法,爲什麼我們不能直接運行會如何呢?我們來簡單嘗試一下哈,控制檯執行如下:

$ java -classpath lab-39-demo-2.2.2.RELEASE.jar cn.iocoder.springboot.lab39.skywalkingdemo.Application
錯誤: 找不到或無法加載主類 cn.iocoder.springboot.lab39.skywalkingdemo.Application

直接找不到 Application 類,因爲它在 BOOT-INF/classes 目錄下,不符合 Java 默認的 jar 包的加載規則。因此,需要通過 JarLauncher 啓動加載。

當然實際還有一個更重要的原因,Java 規定可執行器的 jar 包禁止嵌套其它 jar 包。但是我們可以看到 BOOT-INF/lib 目錄下,實際有 Spring Boot 應用依賴的所有 jar 包。因此,spring-boot-loader 項目自定義實現了 ClassLoader 實現類 LaunchedURLClassLoader,支持加載 BOOT-INF/classes 目錄下的 .class 文件,以及 BOOT-INF/lib 目錄下的 jar 包。

3. JarLauncher

JarLauncher 類是針對 Spring Boot jar 包的啓動類,整體類圖如下所示:

JarLauncher 類圖

友情提示:WarLauncher 類,是針對 Spring Boot war 包的啓動類,後續胖友可以自己瞅瞅,差別並不大哈~

JarLauncher 的源碼比較簡單,如下圖所示:

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);
 }

}

通過 #main(String[] args) 方法,創建 JarLauncher 對象,並調用其 #launch(String[] args) 方法進行啓動。整體的啓動邏輯,其實是由父類 Launcher 所提供,如下圖所示:

Launcher 啓動過程

父類 Launcher 的 #launch(String[] args) 方法,代碼如下:

// Launcher.java

protected void launch(String[] args) throws Exception {
 // <1> 註冊 URL 協議的處理器
 JarFile.registerUrlProtocolHandler();
 // <2> 創建類加載器
 ClassLoader classLoader = createClassLoader(getClassPathArchives());
 // <3> 執行啓動類的 main 方法
 launch(args, getMainClass(), classLoader);
}
  • <1> 處,調用 JarFile 的 #registerUrlProtocolHandler() 方法,註冊 Spring Boot 自定義的 URLStreamHandler 實現類,用於 jar 包的加載讀取。

  • <2> 處,調用自身的 #createClassLoader(List<Archive> archives) 方法,創建自定義的 ClassLoader 實現類,用於從 jar 包中加載類。

  • <3> 處,執行我們聲明的 Spring Boot 啓動類,進行 Spring Boot 應用的啓動。

簡單來說,就是整一個可以讀取 jar 包中類的加載器,保證 BOOT-INF/lib 目錄下的類和 BOOT-classes 內嵌的 jar 中的類能夠被正常加載到,之後執行 Spring Boot 應用的啓動。

下面,我們逐行代碼來看看噢。即將代碼多多,保持淡定,嘿嘿~

3.1 registerUrlProtocolHandler

友情提示:對應 JarFile.registerUrlProtocolHandler(); 代碼段,不要迷路。

JarFile 是 java.util.jar.JarFile 的子類,如下所示:

public class JarFile extends java.util.jar.JarFile {

    // ... 省略其它代碼

}

JarFile 主要增強支持對內嵌的 jar 包的獲取。如下圖所示:

讀取內嵌的 jar 包的演示

OK,介紹完之後,讓我們回到 JarFile 的 #registerUrlProtocolHandler() 方法,註冊 Spring Boot 自定義的 URL 協議的處理器。代碼如下:

// JarFile.java

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

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

/**
 * Register a {@literal 'java.protocol.handler.pkgs'} property so that a
 * {@link URLStreamHandler} will be located to deal with jar URLs.
 */
public static void registerUrlProtocolHandler() {
    // 獲得 URLStreamHandler 的路徑
 String handlers = System.getProperty(PROTOCOL_HANDLER, "");
 // 將 Spring Boot 自定義的 HANDLERS_PACKAGE(org.springframework.boot.loader) 補充上去
 System.setProperty(PROTOCOL_HANDLER, ("".equals(handlers) ? HANDLERS_PACKAGE
   : handlers + "|" + HANDLERS_PACKAGE));
 // 重置已緩存的 URLStreamHandler 處理器們
 resetCachedUrlHandlers();
}

/**
 * Reset any cached handlers just in case a jar protocol has already been used.
 * We reset the handler by trying to set a null {@link URLStreamHandlerFactory} which
 * should have no effect other than clearing the handlers cache.
 *
 * 重置 URL 中的 URLStreamHandler 的緩存,防止 `jar://` 協議對應的 URLStreamHandler 已經創建
 * 我們通過設置 URLStreamHandlerFactory 爲 null 的方式,清空 URL 中的該緩存。
 */
private static void resetCachedUrlHandlers() {
 try {
  URL.setURLStreamHandlerFactory(null);
 } catch (Error ex) {
  // Ignore
 }
}
  • 胖友先跟着註釋,自己閱讀下如上的代碼~

目的很明確,通過將 org.springframework.boot.loader 包設置到 "java.protocol.handler.pkgs" 環境變量,從而使用到自定義的 URLStreamHandler 實現類 Handler,處理 jar: 協議的 URL。

友情提示:這裏我們暫時不深入 Handler 的源碼,避免直接走的太深,丟失了主幹。後續胖友可結合《Java URL 協議擴展實現》文章,進行 Handler 的實現理解。

另外,HandlerTests 提供的單元測試,也是非常有幫助的~

3.2 createClassLoader

友情提示:對應 ClassLoader classLoader = createClassLoader(getClassPathArchives()) 代碼段,不要迷路。

3.2.1 getClassPathArchives

首先,我們先來看看 #getClassPathArchives() 方法,它是由 ExecutableArchiveLauncher 所實現,代碼如下:

// ExecutableArchiveLauncher.java

private final Archive archive;

@Override
protected List<Archive> getClassPathArchives() throws Exception {
 // <1> 獲得所有 Archive
 List<Archive> archives = new ArrayList<>(
   this.archive.getNestedArchives(this::isNestedArchive));
 // <2> 後續處理
 postProcessClassPathArchives(archives);
 return archives;
}

protected abstract boolean isNestedArchive(Archive.Entry entry);

protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
}

友情提示:這裏我們會看到一個 Archive 對象,先可以暫時理解成一個一個的檔案,稍後會清晰認識的~

<1> 處,this::isNestedArchive 代碼段,創建了 EntryFilter 匿名實現類,用於過濾 jar 包不需要的目錄。

// Archive.java

/**
 * Represents a single entry in the archive.
 */
interface Entry {

 /**
  * Returns {@code true} if the entry represents a directory.
  * @return if the entry is a directory
  */
 boolean isDirectory();

 /**
  * Returns the name of the entry.
  * @return the name of the entry
  */
 String getName();

}

/**
 * Strategy interface to filter {@link Entry Entries}.
 */
interface EntryFilter {

 /**
  * Apply the jar entry filter.
  * @param entry the entry to filter
  * @return {@code true} if the filter matches
  */
 boolean matches(Entry entry);

}

這裏在它的內部,調用了 #isNestedArchive(Archive.Entry entry) 方法,它是由 JarLauncher 所實現,代碼如下:

// JarLauncher.java

static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

static final String BOOT_INF_LIB = "BOOT-INF/lib/";

@Override
protected boolean isNestedArchive(Archive.Entry entry) {
    // 如果是目錄的情況,只要 BOOT-INF/classes/ 目錄
 if (entry.isDirectory()) {
  return entry.getName().equals(BOOT_INF_CLASSES);
 }
 // 如果是文件的情況,只要 BOOT-INF/lib/ 目錄下的 `jar` 包
 return entry.getName().startsWith(BOOT_INF_LIB);
}
  • 目的就是過濾獲得,BOOT-INF/classes/ 目錄下的類,以及 BOOT-INF/lib/ 的內嵌 jar 包。

<1> 處,this.archive.getNestedArchives 代碼段,調用 Archive 的 #getNestedArchives(EntryFilter filter) 方法,獲得 archive 內嵌的 Archive 集合。代碼如下:

// Archive.java

/**
 * Returns nested {@link Archive}s for entries that match the specified filter.
 * @param filter the filter used to limit entries
 * @return nested archives
 * @throws IOException if nested archives cannot be read
 */
List<Archive> getNestedArchives(EntryFilter filter) throws IOException;

Archive 接口,是 spring-boot-loader 項目定義的檔案抽象,其子類如下圖所示:

Archive 類圖

  • ExplodedArchive 是針對目錄的 Archive 實現類。

  • JarFileArchive 是針對 jar 包的 Archive 實現類。

友情提示:這塊可能有一丟丟複雜,胖友喫耐心哈~

那麼,我們在 ExecutableArchiveLauncher 的 archive 屬性是怎麼來的呢?答案在 ExecutableArchiveLauncher 的構造方法中,代碼如下:

// ExecutableArchiveLauncher.java

public abstract class ExecutableArchiveLauncher extends Launcher {

 private final Archive archive;

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

 protected ExecutableArchiveLauncher(Archive archive) {
  this.archive = archive;
 }

 // ... 省略其它
}

// Launcher.java
public abstract class Launcher {

 protected final Archive createArchive() throws Exception {
     // 獲得 jar 所在的絕對路徑
  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);
  }
  // 如果是目錄,則使用 ExplodedArchive 進行展開
        // 如果不是目錄,則使用 JarFileArchive
  return (root.isDirectory() ? new ExplodedArchive(root)
    : new JarFileArchive(root));
 }

}

根據根路徑是否爲目錄的情況,創建 ExplodedArchive 或 JarFileArchive 對象。那麼問題就來了,這裏的 root 是什麼呢?艿艿一波騷操作,終於輸出了答案,如下圖所示:

root 是什麼!

root 路徑爲 jar 包的絕對地址,也就是說創建 JarFileArchive 對象。原因是,Launcher 所在包爲 org 下,它的根目錄當然是 jar 包的絕對路徑哈!

😈 現在是不是對 Archive 稍微有點感覺落?繼續附加如下代碼,打印 JarFileArchive 的 #getNestedArchives(EntryFilter filter) 方法的執行結果。

// ========== 附加代碼:
// 創建 Archive 對象
Archive archive = new JarFileArchive(root);
// 創建 EntryFilter 對象
Archive.EntryFilter filter = new Archive.EntryFilter() {

    static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

    static final String BOOT_INF_LIB = "BOOT-INF/lib/";

    @Override
    public boolean matches(Archive.Entry entry) {
        // 如果是目錄的情況,只要 BOOT-INF/classes/ 目錄
        if (entry.isDirectory()) {
            return entry.getName().equals(BOOT_INF_CLASSES);
        }
        // 如果是文件的情況,只要 BOOT-INF/lib/ 目錄下的 `jar` 包
        return entry.getName().startsWith(BOOT_INF_LIB);
    }

};
// 執行讀取
for (Archive item : archive.getNestedArchives(filter)) {
    System.out.println(item.getUrl());
}

// ========== 執行結果:
jar:file:/Users/yunai/Java/SpringBoot-Labs/lab-39/lab-39-demo/target/lab-39-demo-2.2.2.RELEASE.jar!/BOOT-INF/classes!/
jar:file:/Users/yunai/Java/SpringBoot-Labs/lab-39/lab-39-demo/target/lab-39-demo-2.2.2.RELEASE.jar!/BOOT-INF/lib/spring-boot-starter-web-2.2.2.RELEASE.jar!/
jar:file:/Users/yunai/Java/SpringBoot-Labs/lab-39/lab-39-demo/target/lab-39-demo-2.2.2.RELEASE.jar!/BOOT-INF/lib/spring-boot-starter-2.2.2.RELEASE.jar!/
jar:file:/Users/yunai/Java/SpringBoot-Labs/lab-39/lab-39-demo/target/lab-39-demo-2.2.2.RELEASE.jar!/BOOT-INF/lib/spring-boot-2.2.2.RELEASE.jar!/
... 省略其他 jar 包

從執行結果可以看出,BOOT-INF/classes/ 目錄被歸類爲一個 Archive 對象,而 BOOT-INF/lib/ 目錄下的每個內嵌 jar 包都對應一個 Archive 對象。

來來來,回過頭來看看 JarFileArchive 的 #getNestedArchives(EntryFilter filter) 方法的源碼,如下圖所示:

JarFileArchive 的 #getNestedArchives(EntryFilter filter) 方法

現在是不是明白了噢!良心如我,哈哈哈!

友情提示:上述的測試代碼,可以訪問 lab-39-demo 項目查看。

3.2.2 createClassLoader

然後,我再來看看 #createClassLoader(List<Archive> archives) 方法,它是由 ExecutableArchiveLauncher 所實現,代碼如下:

// ExecutableArchiveLauncher.java

protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
 // 獲得所有 Archive 的 URL 地址
    List<URL> urls = new ArrayList<>(archives.size());
 for (Archive archive : archives) {
  urls.add(archive.getUrl());
 }
 // 創建加載這些 URL 的 ClassLoader
 return createClassLoader(urls.toArray(new URL[0]));
}

protected ClassLoader createClassLoader(URL[] urls) throws Exception {
 return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}

基於獲得的 Archive 數組,創建自定義 ClassLoader 實現類 LaunchedURLClassLoader,通過它來加載 BOOT-INF/classes 目錄下的類,以及 BOOT-INF/lib 目錄下的 jar 包中的類。

進一步的解析,我們在「5. LaunchedURLClassLoader」小節中,進行分享哈!

3.3 launch

友情提示:對應 launch(args, getMainClass(), classLoader) 代碼段,不要迷路。

3.3.1 getMainClass

首先,我們先來看看#getMainClass() 方法,它是由 ExecutableArchiveLauncher 所實現,代碼如下:

// ExecutableArchiveLauncher.java

@Override
protected String getMainClass() throws Exception {
    // 獲得啓動的類的全名
 Manifest manifest = this.archive.getManifest();
 String mainClass = null;
 if (manifest != null) {
  mainClass = manifest.getMainAttributes().getValue("Start-Class");
 }
 if (mainClass == null) {
  throw new IllegalStateException(
    "No 'Start-Class' manifest entry specified in " + this);
 }
 return mainClass;
}

從 jar 包的 MANIFEST.MF 文件的 Start-Class 配置項,,獲得我們設置的 Spring Boot 的啓動類。

3.3.2 createMainMethodRunner

然後,我們再來看看 #launch() 方法,它是由 Launcher 所實現,代碼如下:

protected void launch(String[] args, String mainClass, ClassLoader classLoader)
  throws Exception {
    // <1> 設置 LaunchedURLClassLoader 作爲類加載器
 Thread.currentThread().setContextClassLoader(classLoader);
 // <2> 創建 MainMethodRunner 對象,並執行 run 方法,啓動 Spring Boot 應用
 createMainMethodRunner(mainClass, args, classLoader).run();
}

該方法負責最終的 Spring Boot 應用真正的啓動

  • <1> 處:設置「3.2.2 createClassLoader」創建的 LaunchedURLClassLoader 作爲類加載器,從而保證能夠從 jar 加載到相應的類。

  • <2> 處,調用 #createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) 方法,創建 MainMethodRunner 對象,並執行其 #run() 方法來啓動 Spring Boot 應用。

下面,我們來看看 MainMethodRunner 類,負責 Spring Boot 應用的啓動。代碼如下:

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) {
  this.mainClassName = mainClass;
  this.args = (args != null) ? args.clone() : null;
 }

 public void run() throws Exception {
     // <1> 加載 Spring Boot
  Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
  // <2> 反射調用 main 方法
  Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
  mainMethod.invoke(null, new Object[] { this.args });
 }

}
  • <1> 處:通過 LaunchedURLClassLoader 類加載器,加載到我們設置的 Spring Boot 的主啓動類。

  • <2> 處:通過反射調用主啓動類的 #main(String[] args) 方法,啓動 Spring Boot 應用。這裏也告訴了我們答案,爲什麼我們通過編寫一個帶有 #main(String[] args) 方法的類,就能夠啓動 Spring Boot 應用。

4. LaunchedURLClassLoader

LaunchedURLClassLoader 是 spring-boot-loader 項目自定義的類加載器,實現對 jar 包中 META-INF/classes 目錄下的和 META-INF/lib 內嵌的 jar 包中的加載

FROM 《維基百科 —— Java 類加載器》

Java 類加載器是 Java 運行時環境的一個部件,負責動態加載 Java 類到 Java 虛擬機的內存空間中。類通常是按需加載,即第一次使用該類時才加載。

由於有了類加載器,Java 運行時系統不需要知道文件與文件系統。對學習類加載器而言,掌握 Java 的委派概念是很重要的。每個 Java 類必須由某個類加載器裝入到內存。

在「3.2.2 createClassLoader」小節中,我們可以看到 LaunchedURLClassLoader 的創建代碼如下:

// ExecutableArchiveLauncher.java

protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
    // 獲得所有 Archive 的 URL 地址
    List<URL> urls = new ArrayList<>(archives.size());
    for (Archive archive : archives) {
        urls.add(archive.getUrl());
    }
    // 創建加載這些 URL 的 ClassLoader
    return createClassLoader(urls.toArray(new URL[0]));
}

protected ClassLoader createClassLoader(URL[] urls) throws Exception {
 return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}

// LaunchedURLClassLoader.java

public class LaunchedURLClassLoader extends URLClassLoader {

 public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
  super(urls, parent);
 }
 
}
  • 第一個參數 urls,使用的是 Archive 集合對應的 URL 地址們,從而告訴 LaunchedURLClassLoader 讀取 jar 的地址

  • 第二個參數 parent,設置 LaunchedURLClassLoader 的加載器。這裏後續胖友可以理解下,類加載器的雙親委派模型,這裏就拓展開了。

LaunchedURLClassLoader 的實現代碼並不多,我們主要來看看它是如何從 jar 包中加載類的。核心如下圖所示:

  • <1> 處,在通過父類的 #getPackage(String name) 方法獲取不到指定類所在的包時,會通過遍歷 urls 數組,從 jar 包中加載類所在的包。當找到包時,會調用 #definePackage(String name, Manifest man, URL url) 方法,設置包所在的 Archive 對應的 url

  • <2> 處,調用父類的 #loadClass(String name, boolean resolve) 方法,加載對應的類。

如此,我們就實現了通過 LaunchedURLClassLoader 加載 jar 包中內嵌的類。

666. 彩蛋

總體來說,Spring Boot jar 啓動的原理是非常清晰的,整體如下圖所示:

Spring Boot jar 啓動原理

紅色部分,解決 jar 包中的類加載問題:

  • 通過 Archive,實現 jar 包的遍歷,將 META-INF/classes 目錄和 META-INF/lib 的每一個內嵌的 jar 解析成一個 Archive 對象。

  • 通過 Handler,處理 jar: 協議的 URL 的資源讀取,也就是讀取了每個 Archive 裏的內容。

  • 通過 LaunchedURLClassLoader,實現 META-INF/classes 目錄下的類和 META-INF/classes 目錄下內嵌的 jar 包中的類的加載。具體的 URL 來源,是通過 Archive 提供;具體 URL 的讀取,是通過 Handler 提供。

橘色部分,解決 Spring Boot 應用的啓動問題:

  • 通過 MainMethodRunner ,實現 Spring Boot 應用的啓動類的執行。

當然,上述的一切都是通過 Launcher 來完成引導和啓動,通過 MANIFEST.MF 進行具體配置。

😈 生活如此美好,本文就此結束!


另外,本文有兩個部分,胖友可以自己再去擼一擼,玩一玩:

  • WarLauncher 類:實現 Spring Boot war 包的啓動。

  • org.springframework.boot.loader.jar 包:具體讀取 jar 的邏輯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章