SpringBoot應用啟動流程


[TOC]

# 描述
> 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等等。
      org目錄里放的是Springboot加載的class文件。 送上鏈接 https://github.com/spring-projects/spring-boot/tree/master/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader 對應的就是該目錄的源碼。
#####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下一步一步執行后寫下。代碼均來之官方的源碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM