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