參考文檔:
- https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/reference/html/appendix-executable-jar-format.html#executable-jar
- 《Spring Boot編程思想(核心篇)》
Spring Boot可執行Jar結構
普通項目可執行Jar結構如下:
example.jar
|
+-META-INF
| +-MANIFEST.MF
+-mycompany
| +-project
| +-YourClasses.class
其中MANIFEST.MF內容類似
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: XXXgaoch
Created-By: Apache Maven 3.6.1
Build-Jdk: 1.8.0_144
Main-Class: mycompany.project.YourClasses.class
而Spring Boot的Jar包結構如下
example.jar
|
+-META-INF
| +-MANIFEST.MF
+-org
| +-springframework
| +-boot
| +-loader
| +-<spring boot loader classes>
+-BOOT-INF
+-classes
| +-mycompany
| +-project
| +-YourClasses.class
+-lib
+-dependency1.jar
+-dependency2.jar
注意,這裏和普通可執行Jar的區別
- 多了一個org\springframework\boot\loader文件夾
- 我們自己的代碼放到了BOOT-INF\classes下
- 我們依賴的Jar放到了BOOT-INF\lib下
其中MANIFEST.MF內容類似
Manifest-Version: 1.0
Implementation-Title: springboot
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: mycompany.project.YourClasses
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.2.6.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher
注意,這裏和普通可執行Jar的MANIFEST.MF文件區別
- Main函數所在類變成了org.springframework.boot.loader.JarLauncher
- 多了一個Start-Class,指向SpringBoot應用的入口
- 多了Spring-Boot-Classes,指向的目錄BOOT-INF/classes/包含所有編譯後的代碼class和資源文件
- 多了個Spring-Boot-Lib,指向的目錄BOOT-INF/lib/包含所有項目添加的依賴包
探尋JarLauncher
從MANIFEST.MF可得知Jar的啓動類變成了org.springframework.boot.loader.JarLauncher,這個類所在的包可以通過Maven找到
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
JarLauncher源碼如下
// 繼承自ExecutableArchiveLauncher,後面我們再詳細看下ExecutableArchiveLauncher
public class JarLauncher extends ExecutableArchiveLauncher {
// 定義默認Jar中Class所在文件夾
static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
// 定義默認Jar中依賴的類庫所在文件夾
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);
}
// JVM將調用本啓動方法,並把啓動參數傳遞到args
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
}
類中最關鍵的是new JarLauncher().launch(args),其代碼如下
protected void launch(String[] args) throws Exception {
// 註冊Spring Boot的LaunchedURLClassLoader,提供讀取從Jar包中內部jar包的能力,也就是BOOT-INF/lib下的Jar包
JarFile.registerUrlProtocolHandler();
// 創建LaunchedURLClassLoader實例
ClassLoader classLoader = createClassLoader(getClassPathArchives());
// getMainClass()獲取MANIFEST.MF文件中定義的Start-Class,也就是我們編寫的Boot啓動類
launch(args, getMainClass(), classLoader);
}
getMainClass() 內容如下:
@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;
}
lauch創建MainMethodRunner實例,並調用其run方法,來實現對MainClass的調用,並傳入啓動參數。
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run();
}
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 {
Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[] { this.args });
}
}