SpringBoot項目的spring-boot-maven-plugin插件打包原理


前言

在使用SpringBoot的項目中,我們需要配置spring-boot-maven-plugin插件

<plugin>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-maven-plugin</artifactId>             
</plugin>

這是SpringBoot自己提供的插件,它可以在maven的生命周期package后重新打包,生成自己的jar包結構。插件提供的功能如下

核心為repackage,它會在package之后執行,生成一個新的jar包,將之前的jar包命名為 xxx.original。

打包原理

生成的xxx.original的jar包結構為

不包含項目依賴的第三方jar包,MANIFEST.MF文件內容為

Manifest-Version: 1.0
Created-By: Maven Archiver 3.4.0
Build-Jdk-Spec: 11
Implementation-Title: springbootfirst
Implementation-Version: 0.0.1-SNAPSHOT

SpringBoot插件生成的jar包結構為

BOOT-INF/classes 中包含項目所有的class文件,BOOT-INF/lib 下包含項目依賴的第三方jar包,MANIFEST.MF文件內容為

Manifest-Version: 1.0
Created-By: Maven Archiver 3.4.0
Build-Jdk-Spec: 11
Implementation-Title: springbootfirst
Implementation-Version: 0.0.1-SNAPSHOT
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.imooc.springbootfirst.SpringbootfirstApplication
Spring-Boot-Version: 2.1.6.RELEASE
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/

插件是如何找到啟動類的

描述文件中的Start-Class就是我們項目的啟動類,我們可以查看插件源碼來分析

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <version>2.2.1.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-loader</artifactId>
  <version>2.2.1.RELEASE</version>
</dependency>

先引入插件的maven依賴

RepackageMojo就是repackage打包要執行的邏輯,核心類為Repackager

在插件創建MANIFEST.MF文件過程中,如果我們沒有配置MainClass屬性,就會通過MainClassFinder類來查找MainClass

private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication";

protected String findMainMethod(JarFile source) throws IOException {
		return MainClassFinder.findSingleMainClass(source, this.layout.getClassesLocation(),
				SPRING_BOOT_APPLICATION_CLASS_NAME);
	}

在所有類中查找包含SpringBootApplication注解且包含main方法的類,並當做啟動類,內部通過ASM字節碼庫來解析class文件得到類信息。

啟動流程

java -jar springbootfirst-0.0.1-SNAPSHOT.jar

java啟動jar包會找META-INF/MANIFEST.MF文件中的Main-Class來啟動,SpringBoot插件最終生成的Main-Class為 org.springframework.boot.loader.JarLauncher類。

/**
 * jar包類型的啟動器
 *
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @since 1.0.0
 */
public class JarLauncher extends ExecutableArchiveLauncher {

	static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

	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);
	}

	public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);
	}

}

最終運行的是MANIFEST.MF文件中Start-Class,值為com.imooc.springbootfirst.SpringbootfirstApplication,其實就是我們項目中配置的啟動類。

/**
 * 啟動器
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @since 1.0.0
 */
public abstract class Launcher {

	/**
	 * 啟動流程
	 */
	protected void launch(String[] args) throws Exception {
		JarFile.registerUrlProtocolHandler();
		//根據BOOT-INF/classes下的class文件和BOOT-INF/lib下的第三方jar包創建Archive
		//創建的ClassLoader為LaunchedURLClassLoader類型
		ClassLoader classLoader = createClassLoader(getClassPathArchives());
		launch(args, getMainClass(), classLoader);
	}

	/**
	 * 創建新的LaunchedURLClassLoader,從多個URL中加載class
	 */
	protected ClassLoader createClassLoader(URL[] urls) throws Exception {
		return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
	}

	/**
	 * 將新的類加載器設置到線程上下文中,並啟動應用程序
	 */
	protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
		Thread.currentThread().setContextClassLoader(classLoader);
		createMainMethodRunner(mainClass, args, classLoader).run();
	}

	/**
	 * 創建一個Main方法運行器來啟動應用程序
	 */
	protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
		return new MainMethodRunner(mainClass, args);
	}

}

創建新的ClassLoader類型LaunchedURLClassLoader,從BOOT-INF/classes下和BOOT-INF/lib下的所有jar包中加載class。加載我們整個項目的都是LaunchedURLClassLoader類加載器。

/**
 * 一個執行Main方法的工具類
 *
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @since 1.0.0
 */
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 {
		//使用線程上下文類加載器加載MainClass,就是我們項目中的SpringbootfirstApplication
		Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
		//執行Main方法
		Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
		mainMethod.invoke(null, new Object[] { this.args });
	}
}

參考

springboot 打包插件spring-boot-maven-plugin打包機制及內部結構分析


免責聲明!

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



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