[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!/
jar:
file:/C:/Users/angy/AppData/Local/Temp/junit7462285095317089505/archive.jar!/BOOT-INF/lib/foo.jar!/
```
有了上面資源的保障,下面來看看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> archives) throws 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[] urls) throws 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(mainClass, args, classLoader).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(mainClass, args);
}
```
以上是整個Jar的啟動流程,本人是在tools源碼里debug下一步一步執行后寫下。代碼均來之官方的源碼。