Spring Boot(1) 啓動分析

參考文檔:

  1. https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/reference/html/appendix-executable-jar-format.html#executable-jar
  2. 《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的區別

  1. 多了一個org\springframework\boot\loader文件夾
  2. 我們自己的代碼放到了BOOT-INF\classes下
  3. 我們依賴的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文件區別

  1. Main函數所在類變成了org.springframework.boot.loader.JarLauncher
  2. 多了一個Start-Class,指向SpringBoot應用的入口
  3. 多了Spring-Boot-Classes,指向的目錄BOOT-INF/classes/包含所有編譯後的代碼class和資源文件
  4. 多了個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 });
	}

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章