1. 程式人生 > >Springboot原始碼分析之jar探祕

Springboot原始碼分析之jar探祕

摘要:

  • 利用IDEA等工具打包會出現springboot-0.0.1-SNAPSHOT.jar,springboot-0.0.1-SNAPSHOT.jar.original,前面說過它們之間的關係了,接下來我們就一探究竟,它們之間到底有什麼聯絡。

檔案對比:

  • 進入target目錄,unzip springboot-0.0.1-SNAPSHOT.jar -d jar命令將springboot-0.0.1-SNAPSHOT.jar解壓到jar目錄

  • 進入target目錄,unzip springboot-0.0.1-SNAPSHOT.jar.original -d original命令將springboot-0.0.1-SNAPSHOT.jar.original解壓到original目錄

前面文章分析過springboot-0.0.1-SNAPSHOT.jar.original不能執行,將它進行repackage後生成springboot-0.0.1-SNAPSHOT.jar就成了我們的可執行fat jar,對比上面檔案會發現可執行 fat jar和original jar目錄不一樣,最關鍵的地方是多了org.springframework.boot.loader這個包,這個就是我們平時java -jar springboot-0.0.1-SNAPSHOT.jar命令啟動的奧妙所在。MANIFEST.MF檔案裡面的內容包含了很多關鍵的資訊

Manifest-Version: 1.0
Start-Class: com.github.dqqzj.springboot.SpringbootApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.1.6.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher

相信不用多說大家都能明白Main-Class: org.springframework.boot.loader.JarLauncher是我們 java -jar命令啟動的入口,後續會進行分析,Start-Class: com.github.dqqzj.springboot.SpringbootApplication才是我們程式的入口主函式。

Springboot jar啟動原始碼分析

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);
    }
   /**
    * 判斷是否歸檔檔案還是檔案系統的目錄 可以猜想基於檔案系統一樣是可以啟動的
    */
    protected boolean isNestedArchive(Entry entry) {
        return entry.isDirectory() ? entry.getName().equals("BOOT-INF/classes/") : entry.getName().startsWith("BOOT-INF/lib/");
    }

    public static void main(String[] args) throws Exception {
    /**
     * 進入父類初始化構造器ExecutableArchiveLauncher
     * launch方法交給Launcher執行
     */
        (new JarLauncher()).launch(args);
    }
}

public abstract class ExecutableArchiveLauncher extends Launcher {
    private final Archive archive;

    public ExecutableArchiveLauncher() {
        try {
    /**
     * 使用父類Launcher載入資源,包括BOOT-INF的classes和lib下面的所有歸檔檔案
     */
            this.archive = this.createArchive();
        } catch (Exception var2) {
            throw new IllegalStateException(var2);
        }
    }

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

    protected final Archive getArchive() {
        return this.archive;
    }
    /**
     * 從歸檔檔案中獲取我們的應用程式主函式
     */
    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);
        } else {
            return mainClass;
        }
    }

    protected List<Archive> getClassPathArchives() throws Exception {
        List<Archive> archives = new ArrayList(this.archive.getNestedArchives(this::isNestedArchive));
        this.postProcessClassPathArchives(archives);
        return archives;
    }

    protected abstract boolean isNestedArchive(Entry entry);

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

public abstract class Launcher {
    public Launcher() {
    }

    protected void launch(String[] args) throws Exception {
      /**
       *註冊協議處理器,由於Springboot是 jar in jar 所以要重寫jar協議才能讀取歸檔檔案
       */
        JarFile.registerUrlProtocolHandler();
        ClassLoader classLoader = this.createClassLoader(this.getClassPathArchives());
      /**
       * this.getMainClass()交給子類ExecutableArchiveLauncher實現
       */
        this.launch(args, this.getMainClass(), classLoader);
    }

    protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
        List<URL> urls = new ArrayList(archives.size());
        Iterator var3 = archives.iterator();

        while(var3.hasNext()) {
            Archive archive = (Archive)var3.next();
            urls.add(archive.getUrl());
        }

        return this.createClassLoader((URL[])urls.toArray(new URL[0]));
    }
    /**
     * 該類載入器是fat jar的關鍵的一處,因為傳統的類載入器無法讀取jar in jar模型,所以springboot進行了自己實現
     */
    protected ClassLoader createClassLoader(URL[] urls) throws Exception {
        return new LaunchedURLClassLoader(urls, this.getClass().getClassLoader());
    }
   
    protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
        Thread.currentThread().setContextClassLoader(classLoader);
        this.createMainMethodRunner(mainClass, args, classLoader).run();
    }
    /**
     * 建立應用程式主函式執行器
     */
    protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
        return new MainMethodRunner(mainClass, args);
    }

    protected abstract String getMainClass() throws Exception;

    protected abstract List<Archive> getClassPathArchives() throws Exception;
   /**
          * 得到我們的啟動jar的歸檔檔案
            */
    protected final Archive createArchive() throws Exception {
        ProtectionDomain protectionDomain = this.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");
        } else {
            File root = new File(path);
            if (!root.exists()) {
                throw new IllegalStateException("Unable to determine code source archive from " + root);
            } else {
                return (Archive)(root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
            }
        }
    }
}

public class MainMethodRunner {
    private final String mainClassName;
    private final String[] args;
   
    public MainMethodRunner(String mainClass, String[] args) {
        this.mainClassName = mainClass;
        this.args = args != null ? (String[])args.clone() : null;
    }
    /**
     * 最終執行的方法,可以發現是利用的反射呼叫的我們應用程式的主函式
     */
    public void run() throws Exception {
        Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
        Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
        mainMethod.invoke((Object)null, this.args);
    }
}

小結:

內容太多了,未涉及歸檔檔案,協議處理器,打包war同樣的可以用命令啟動等,感興趣的讀者請親自去除錯一番,新增依賴

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-loader</artifactId>
        </dependency>

IDEA進行啟動類的配置

相關推薦

Springboot原始碼分析jar探祕

摘要: 利用IDEA等工具打包會出現springboot-0.0.1-SNAPSHOT.jar,springboot-0.0.1-SNAPSHOT.jar.original,前面說過它們之間的關係了,接下來我們就一探究竟,它們之間到底有什麼聯絡。 檔案對比: 進入target目錄,unzip sprin

SpringBoot原始碼分析---SpringBoot專案啟動類SpringApplication淺析

原始碼版本 本文原始碼採用版本為SpringBoot 2.1.0BUILD,對應的SpringFramework 5.1.0.RC1 注意:本文只是從整體上梳理流程,不做具體深入分析 SpringBoot入口類 @SpringBootAp

Springboot原始碼分析專案結構

摘要: 無論是從IDEA還是其他的SDS開發工具亦或是https://start.spring.io/ 進行解壓,我們都會得到同樣的一個pom.xml檔案 xml <?xml version="1.0" encoding="UTF-8"?> <project xm

Springboot原始碼分析番外篇

摘要: 大家都知道註解是實現了java.lang.annotation.Annotation介面,眼見為實,耳聽為虛,有時候眼見也不一定是真實的。 /** * The common interface extended by all annotation types. Note that

Springboot原始碼分析EnableAspectJAutoProxy

摘要: Spring Framwork的兩大核心技術就是IOC和AOP,AOP在Spring的產品線中有著大量的應用。如果說反射是你通向高階的基礎,那麼代理就是你站穩高階的底氣。AOP的本質也就是大家所熟悉的CGLIB動態代理技術,在日常工作中想必或多或少都用過但是它背後的祕密值得我們去深思。本文主要從Spr

Springboot原始碼分析代理三板斧

摘要: 在Spring的版本變遷過程中,註解發生了很多的變化,然而代理的設計也發生了微妙的變化,從Spring1.x的ProxyFactoryBean的硬編碼島Spring2.x的Aspectj註解,最後到了現在廣為熟知的自動代理。 說明: ProxyConfig代理的相關配置類 AdvisedSupp

Springboot原始碼分析AbstractAdvisorAutoProxyCreator

摘要: Spring的代理在上層中主要分為ProxyCreatorSupport和ProxyProcessorSupport,前者是基於代理工廠,後者是基於後置處理器,也可以認為後置就是自動代理器。當spring容器中需要進行aop進行織入的bean較多時,簡單採用ProxyFacotryBean無疑會增加很

Springboot原始碼分析TargetSource

摘要: 其實我第一次看見這個東西的時候也是不解,代理目標源不就是一個class嘛還需要封裝幹嘛。。。 其實proxy代理的不是target,而是TargetSource,這點非常重要,一定要分清楚!!! 通常情況下,一個代理物件只能代理一個target,每次方法呼叫的目標也是唯一固定的target。但是,如果

Springboot原始碼分析@Transactional

摘要: 對SpringBoot有多瞭解,其實就是看你對Spring Framework有多熟悉~ 比如SpringBoot大量的模組裝配的設計模式,其實它屬於Spring Framework提供的能力。SpringBoot大行其道的今天,基於XML配置的Spring Framework的使用方式註定已成為過去

Springboot原始碼分析事務攔截和管理

摘要: 在springboot的自動裝配事務裡面,InfrastructureAdvisorAutoProxyCreator ,TransactionInterceptor,PlatformTransactionManager這三個bean都被裝配進來了,InfrastructureAdvisorAutoPr

Springboot原始碼分析事務問題

摘要: 事務在後端開發中無處不在,是資料一致性的最基本保證。要明白進事務的本質就是進到事務切面的代理方法中,最常見的是同一個類的非事務方法呼叫一個加了事務註解的方法沒進入事務。我們以cglib代理為例,由於Spring的對於cglib AOP代理的實現,進入被代理方法的時候實際上已經離開了“代理這一層殼子”,

Springboot原始碼分析Spring迴圈依賴揭祕

摘要: 若你是一個有經驗的程式設計師,那你在開發中必然碰到過這種現象:事務不生效。或許剛說到這,有的小夥伴就會大驚失色了。Spring不是解決了迴圈依賴問題嗎,它是怎麼又會發生迴圈依賴的呢?,接下來就讓我們一起揭祕Spring迴圈依賴的最本質原因。 Spring迴圈依賴流程圖 Spring迴圈依賴發生原因

Springboot原始碼分析代理物件內嵌呼叫

摘要: 關於這個話題可能最多的是@Async和@Transactional一起混用,我先解釋一下什麼是代理物件內嵌呼叫,指的是一個代理方法呼叫了同類的另一個代理方法。首先在這兒我要宣告事務直接的巢狀呼叫除外,至於為什麼,是它已經將資訊儲存線上程級別了,是不是又點兒抽象,感覺吃力,可以看看我前面關於事務的介紹。

Springboot原始碼分析TypeFilter魔力

摘要: 在平常的開發中,不知道大家有沒有想過這樣一個問題,為什麼我們自定義註解的時候要使用spring的原生註解(這裡指的是類似@Component,@Service........),要麼就是 隨便弄個註解,搭配自己的切面程式設計來實現某些業務邏輯。這篇文章主要給大家分享一下,如何脫離Spring原生註解自

springboot原始碼分析4-springbootSpringFactoriesLoader使用

摘要:本文我們重點分析一下Spring框架中的SpringFactoriesLoader類以及META-INF/spring.factories的使用。在詳細分析之前,我們可以思考一個問題?在我們設計一套API供別人呼叫的時候,如果同一個功能的要求特別多,或者同一個介面要面對

springboot原始碼分析5-springboot命令列引數以及原理

摘要:本文我們重點分析一下Springboot框架中的命令列引數的使用以及框架內部處理的命令列引數的原理。眾所周知,springboot專案可以有兩種方式啟動,第一種使用jar包;第二種使用war包。在使用jar方式的時候,我們可以在啟動jar包的時候設定一些命令引數。1.1

springboot原始碼分析6-springbootPropertySource類初探

在springboot原始碼分析5-springboot之命令列引數以及原理一文中,我們看到了例項化Source類的時候,會去先例項化其父類SimpleCommandLinePropertySource。SimpleCommandLinePropertySource類的建構函

Spark原始碼分析Spark Shell(上)

https://www.cnblogs.com/xing901022/p/6412619.html 文中分析的spark版本為apache的spark-2.1.0-bin-hadoop2.7。 bin目錄結構: -rwxr-xr-x. 1 bigdata bigdata 1089 Dec

Netty 原始碼分析拆包器的奧祕

為什麼要粘包拆包 為什麼要粘包 首先你得了解一下TCP/IP協議,在使用者資料量非常小的情況下,極端情況下,一個位元組,該TCP資料包的有效載荷非常低,傳遞100位元組的資料,需要100次TCP傳送,100次ACK,在應用及時性要求不高的情況下,將這100個有效資料拼接成一個數據包,那會縮短到一個TCP資

Android原始碼分析為什麼在onCreate() 和 onResume() 獲取不到 View 的寬高

轉載自:https://www.jianshu.com/p/d7ab114ac1f7 先來看一段很熟悉的程式碼,可能在最開始接觸安卓的時候,大部分人都寫過的一段程式碼;即嘗試在 onCreate() 和 onResume() 方法中去獲取某個 View 的寬高資訊: 但是列印輸出後,我們會發