SpringBoot工程打包后執(zhí)行Java?-Jar就能啟動的步驟原理
本文主要分享SpringBoot工程項目如何打包成一個可直接通過java -jar執(zhí)行的jar,并且簡單分析其啟動步驟原理。
1.SpringBoot如何打包成一個可執(zhí)行jar?
SpringBoot打包成成一個可執(zhí)行jar需要依賴一個maven打包插件spring-boot-maven-plugin,如下所示在pom文件結尾的build節(jié)點添加依賴,同時將src/main/java和src/main/resources打入jar里面。
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.*</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.*</include> </includes> </resource> </resources> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
此時再執(zhí)行maven package打包成jar(最后打包完成在target目錄下):
此時我們直接通過cmd控制臺執(zhí)行命令:java -jar boot-first-1.0.0-SNAPSHOT.jar 可以看到啟動成功。
相關日志已經打印出來了,此時我們再通過postman驗證一下編碼中的接口是否生效:
程序中的接口代碼如下:
2.SpringBoot打包成的jar為何可以直接Java -jar執(zhí)行?
java程序的入口是main方法 ,如果一個jar能夠通過java -jar執(zhí)行起來,肯定離不開main方法。那springboot通過spring-boot-maven-plugin插件打包成的jar通常稱為fat jar,里面不僅僅包含了程序代碼還包括其他引用的依賴的jar。那這個fat jar的main方法入口在哪兒呢?我們通過解壓該jar可以看到,在META-INF下的有一個MANIFEST.MF文件:
其中MANIFEST.MF中內容如下,其中比較重要的幾行信息:
Start-Class: com.xren.bootfirst.BootFirstApplication ##程序開始類
Spring-Boot-Classes: BOOT-INF/classes/ ##加載的class
Spring-Boot-Lib: BOOT-INF/lib/ ##加載的內部jar位置
Main-Class: org.springframework.boot.loader.JarLauncher ## boot啟動類
綜上可知,SpringBoot項目通過引入打包插件spring-boot-maven-plugin生成特定的主清單文件MANIFEST.MF,其中包含了程序的入口類和相關啟動依賴。
3.一窺SpringBoot初啟動
通用的jar包如果能被java -jar執(zhí)行,只需要其MANIFEST.MF文件中有 Main-Class配置項即可。而此處SpringBoot的jar中的MANIFEST.MF文件里面不僅僅包含Main-Class 還有Start-Class、Spring-Boot-Classes、Spring-Boot-Lib等相關信息,那他們是如何運行的呢?我們還是要回到該jar主類org.springframework.boot.loader.JarLauncher來一窺究竟。
查看org.springframework.boot.loader.JarLauncher類需要pom引入spring-boot-loader,如下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-loader</artifactId> <version>2.2.7.RELEASE</version> </dependency>
引入后其中JarLaunche類代碼如下,它包含一個main方法,并且繼承一個父類ExecutableArchiveLauncher。
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); } protected boolean isNestedArchive(Entry entry) { return entry.isDirectory() ? entry.getName().equals("BOOT-INF/classes/") : entry.getName().startsWith("BOOT-INF/lib/"); } public static void main(String[] args) throws Exception { (new JarLauncher()).launch(args); } }
從main方法進入我們需要關注一個launch方法,其中的三行
- 注冊一個jar處理類
- 獲取一個類加載器(通過路徑加載lib和classs)
- 通過獲取主啟動類和類加載器運行啟動。
protected void launch(String[] args) throws Exception { JarFile.registerUrlProtocolHandler(); ClassLoader classLoader = this.createClassLoader(this.getClassPathArchives()); this.launch(args, this.getMainClass(), classLoader); }
追尋this.getMainClass() 方法,其會指向上述的父類ExecutableArchiveLauncher中,此處就回到了我們開始說的Start-Class配置項。
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); } else { return mainClass; } }
getMainClass()方法中的this.archive.getManifest() 就是獲取jar下的META-INF/MANIFEST.MF文件。此處追尋到Archive的子類ExplodedArchive中,如下代碼獲取清單文件:
private File getManifestFile(File root) { File metaInf = new File(root, "META-INF"); return new File(metaInf, "MANIFEST.MF"); }
到此時清單文件中的Start-Class、Spring-Boot-Classes、Spring-Boot-Lib都已找到,準備啟動!
回到org.springframework.boot.loader.Launcher的launch方法。
- 當前線程設置類加載器
- 創(chuàng)建主方法并運行(主方法即為Start-Class指向的類)
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception { Thread.currentThread().setContextClassLoader(classLoader); this.createMainMethodRunner(mainClass, args, classLoader).run(); }
以上兩行代碼具體實現在org.springframework.boot.loader.MainMethodRunner類中,run方法即為最后的啟動邏輯
- 通過當前線程類加載器載入清單文件Start-Class配置的主類。
- 加載后獲取主類中的main方法(XXXApplication中的main方法)。
- 反射執(zhí)行該XXXApplication中的main方法。
public MainMethodRunner(String mainClass, String[] args) { this.mainClassName = mainClass; this.args = args != null ? (String[])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((Object)null, this.args); }
至此,從執(zhí)行java -jar命令,SpringBoot打包的jar啟動到程序中XXXApplication中的main方法這一階段我們就初步分析完了!
總結:SpringBoot通過引入打包插件spring-boot-maven-plugin將其相關信息寫入到java-jar的META-INF/MANIFEST.MF文件中,而后通過清單文件中的主入口Main-Class配置的org.springframework.boot.loader.JarLauncher類加載相關class、lib和應用程序主類Start-Class配置的XXXApplication類,再反射獲取XXXApplication類中我們業(yè)務定義的main方法啟動運行。
到此這篇關于SpringBoot工程打包后為何執(zhí)行Java -Jar就能啟動?的文章就介紹到這了,更多相關springboot打包執(zhí)行ava -Jar啟動內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
springboot讀取resource配置文件生成容器對象的示例代碼
這篇文章主要介紹了springboot讀取resource配置文件生成容器對象的示例代碼,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07Spring?Boot?基于?SCRAM?認證集成?Kafka?的過程詳解
在本篇文章中,我們將探討如何在?Spring?Boot?應用中集成?Kafka?并使用?SCRAM?認證機制進行安全連接,并實現動態(tài)創(chuàng)建賬號、ACL?權限、Topic,以及生產者和消費者等操作,感興趣的朋友跟隨小編一起看看吧2024-08-08