1. 程式人生 > >SpringBoot應用啟動流程

SpringBoot應用啟動流程

> SpringBoot在微服務上應用是越來越多,同樣教程也比較多,但是我相信會有人跟我一樣的迷惑,它的載入過程是什麼樣的,要經過哪些類,然後又為什麼會能直接把應用打包成jar/war,然後就可以直接執行?本次使用的SpringBoot版本1.5.2。 # 從快速啟動Demo中看載入過程     有很多人都下載了Springboot 版的eclipse,構建專案也比較方便,其實我感覺也挺好的,但是我習慣了用IDEA,所以這次就從官網上初始化一個新的專案 ## 準備快速啟動專案 ### 在官網初始化一個新的專案 ### 執行打包        為了給大家展示jar/war打包的內容,然後我打包了兩次,同時把相關的內容給大家貼出來。          war打完包的目錄結構          區別不大jar打包的時候是BOOT-INF放我們自己開發的程式碼,war打包的時候是WEB-INF。 ####  我們先看打包的META-INF裡的MANIFEST.MF檔案。 > jar MANIFEST.MF ``` Manifest-Version: 1.0 Implementation-Title: demo Implementation-Version: 0.0.1-SNAPSHOT Archiver-Version: Plexus Archiver Built-By: angy Implementation-Vendor-Id: com.hiext Spring-Boot-Version: 1.5.2.RELEASE Implementation-Vendor: Pivotal Software, Inc. Main-Class: org.springframework.boot.loader.JarLauncher Start-Class: com.hiext.DemoApplication Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Created-By: Apache Maven 3.0.5 Build-Jdk: 1.8.0_111 Implementation-URL: http://projects.spring.io/spring-boot/authorizeServer/  `` `
> war MANIFEST.MF `` ` Manifest-Version: 1.0 Implementation-Title: demo Implementation-Version: 0.0.1-SNAPSHOT Archiver-Version: Plexus Archiver Built-By: angy Implementation-Vendor-Id: com.hiext Spring-Boot-Version: 1.5.2.RELEASE Implementation-Vendor: Pivotal Software, Inc. Main-Class: org.springframework.boot.loader.WarLauncher Start-Class: com.hiext.DemoApplication Spring-Boot-Classes: WEB-INF/classes/ Spring-Boot-Lib: WEB-INF/lib/ Created-By: Apache Maven 3.0.5 Build-Jdk: 1.8.0_111 Implementation-URL: http://projects.spring.io/spring-boot/authorizeServer/ ```
從檔案的比較上來看載入的不同的類,然後SpringBoot的程式碼也放在不同的位置。 #### 細說MANIFEST.MF檔案 > 這個 manifest 檔案定義了與擴充套件和包相關的資料,是java打包必不可少的一個檔案。這個檔案也描述了jar包的一些很多資訊,主要分為一下幾類 * 1. 一般屬性             1. Manifest-Version

             用來定義manifest檔案的版本,例如:Manifest-Version: 1.0
           2. Created-By
             宣告該檔案的生成者,一般該屬性是由jar命令列工具生成的,例如:Created-By: Apache Ant 1.5.1


           3. Signature-Version
              定義jar檔案的簽名版本
           4. Class-Path
             應用程式或者類裝載器使用該值來構建內部的類搜尋路徑

* 2. 應用程式相關屬性 * 3. 包擴充套件屬性 我們重點看應用程式相關的屬性,就是Main-class,這是jar的入口類,也就是要求這個類必須是可執行的類。 一旦定義了該屬性即可通過 java -jar x.jar來執行該jar檔案。 Start-Class的配置就是我們自己寫的main函數了。 ####  其他資料夾的介紹      BOOT-INF/WEB-INF 這裡放的是我們自己要寫開發的程式碼然後編譯後的class與相關的jar等等。 #####Archive的概念  * archive即歸檔檔案,這個概念在linux下比較常見  * 通常就是一個tar/zip格式的壓縮包 * jar是zip格式 在spring boot裡,抽象出了Archive的概念。 一個archive可以是一個jar(JarFileArchive),也可以是一個檔案目錄(ExplodedArchive)。可以理解為Spring boot抽象出來的統一訪問資源的層。 我們看下Archive這個介面的定義: ``` package org.springframework.boot.loader.archive; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.jar.Manifest; import org.springframework.boot.loader.Launcher; /**  * An archive that can be launched by the {@link Launcher}.  *  * @author Phillip Webb  * @see JarFileArchive  */ public interface Archive extends Iterable<Archive.Entry> { /** * Returns a URL that can be used to load the archive. * @return the archive URL * @throws MalformedURLException if the URL is malformed */ URL getUrl() throws MalformedURLException; /** * Returns the manifest of the archive. * @return the manifest * @throws IOException if the manifest cannot be read */ Manifest getManifest() throws IOException; /** * 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; /** * 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); } } ``` > Archive的實現有兩個一個是ExplodedArchive 一個是JarFileArchive ,就是對應上面說的兩種訪問資源的形式 一種是資料夾 一種是直接訪問jar。從介面來看,每一個資源都有一個URL。 ```
jar:file:/C:/Users/angy/AppData/Local/Temp/junit7462285095317089505/archive.jar!/BOOT-INF/classes!/ ``` 有了上面資源的保障,下面來看看JarLauncher 在MANIFEST.MF裡可以看到Main函式載入的是JarLauncher,下面來分析它的工作流程 同樣JarLauncher的原始碼在上面的SpringBoot的tools裡,JarLauncher 繼承了ExecutableArchiveLauncher類,然後ExecutableArchiveLauncher又繼承了Launcher類。 從JarLauncher 入手,JarLauncher 在構造物件的時候是呼叫ExecutableArchiveLauncher的構造方法,需要一個Archive的實現。 ```     protected ExecutableArchiveLauncher(Archive archive) {         this.archive = archive;     } ``` Archive是在Launcher類中建立的 ```     protected final Archive createArchive() throws Exception {         ProtectionDomain protectionDomain = getClass().getProtectionDomain();         CodeSource codeSource = protectionDomain.getCodeSource();         URI location = (codeSource == null ? null : codeSource.getLocation().toURI());         String path = (location == null ? null : location.getSchemeSpecificPart());         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);         }         return (root.isDirectory() ? new ExplodedArchive(root)                 : new JarFileArchive(root));   //返回其他Jar     } ``` JarLauncher先找到自己所在的jar,然後通過Launcher來呼叫抽象的方法getClassPathArchives()來找到其他的Jar包。下面是ExecutableArchiveLauncher類getClassPathArchives()的實現。 ```     protected List<Archive> getClassPathArchives() throws Exception {         List<Archive> archives = new ArrayList<Archive>(                 this.archive.getNestedArchives(new EntryFilter() {                     @Override                     public boolean matches(Entry entry) {                         return isNestedArchive(entry);                     }                 }));         postProcessClassPathArchives(archives);         return archives;     } ``` 從上面可以看到是通過getNestedArchives函式來獲取到jar包下lib下面的所有jar檔案,並建立為List。然後呼叫Launcher類裡面的createClassLoader方法。用於返回LaunchedURLClassLoader的啟動載入器。 ```     /**      * Create a classloader for the specified archives.      * @param archives the archives      * @return the classloader      * @throws Exception if the classloader cannot be created      */     protected ClassLoader createClassLoader(List<Archive> archivesthrows Exception {         List<URL> urls = new ArrayList<URL>(archives.size());         for (Archive archive : archives) {             urls.add(archive.getUrl());         }         return createClassLoader(urls.toArray(new URL[urls.size()]));     }     /**      * Create a classloader for the specified URLs.      * @param urls the URLs      * @return the classloader      * @throws Exception if the classloader cannot be created      */     protected ClassLoader createClassLoader(URL[] urlsthrows Exception {         return new LaunchedURLClassLoader(urls, getClass().getClassLoader());     } ``` 建立好ClassLoader之後,再從MANIFEST.MF裡讀取到Start-Class,即com.hiext.DemoApplication,以下是讀取Start-Class的方法。 ```     @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的Start-class後通過launch方法來建立執行緒,並執行main函式。 ```     /**      * Launch the application given the archive file and a fully configured classloader.      * @param args the incoming arguments      * @param mainClass the main class to run      * @param classLoader the classloader      * @throws Exception if the launch fails      */     protected void launch(String[] args, String mainClass, ClassLoader classLoader)             throws Exception {         Thread.currentThread().setContextClassLoader(classLoader);         createMainMethodRunner(mainClassargsclassLoader).run();     }     /**      * Create the {@code MainMethodRunner} used to launch the application.      * @param mainClass the main class      * @param args the incoming arguments      * @param classLoader the classloader      * @return the main method runner      */     protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args,             ClassLoader classLoader) {         return new MainMethodRunner(mainClassargs);     } ``` 以上是整個Jar的啟動流程,本人是在tools原始碼裡debug下一步一步執行後寫下。程式碼均來之官方的原始碼。 知識碎片,重在整理,路很長,一步一個腳印,就好。