javascript
SpringBoot - 探究Spring Boot应用是如何通过java -jar 启动的
文章目錄
- Pre
- 引導
- 新建工程 打包 啟動
- java -jar 干啥的
- 打包插件
- spring-boot-maven-plugin簡介
- 包結構
- META-INF內容
- Archive的概念
- JarFile
- JarLauncher工作流程
- 小結
Pre
大家開發的基于Spring Boot 的應用 ,jar形式, 發布的時候,絕大部分都是使用java -jar 啟動。 得益于Spring Boot 的封裝 , 再也不用操心搭建tomcat等相關web容器le , 一切變得非常美好, 那SpringBoot是怎么做到的呢?
引導
新建工程 打包 啟動
我們新創建一個Spring Boot的工程
其中打包的配置為
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>先打包一下
查看target目錄
然后啟動
java -jar 干啥的
我們先看看 java -jar 干了啥 ?
在oracle官網找到了該命令的描述:
If the -jar option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class manifest header in its source code.使用-jar參數時,后面的參數是的jar 【spring-0.0.1-SNAPSHOT.jar】,該jar文件中包含的是class和資源文件; 在manifest文件中有Main-Class的定義;Main-Class的源碼中指定了整個應用的啟動類;
簡單來說: java -jar會去找jar中的manifest文件,去找到Main-Class對應的真正的啟動類;
那看看去吧
咦 ,這個Main-Class 是Spring Boot 的。
我們還看到有個Start Class
官方文檔中,只提到過Main-Class ,并沒有提到Start-Class;
Start-Class的值是com.artisan.spring.Application,這是我們的java代碼中的唯一類,包含main方法, 是能夠真正的應用啟動類
所以問題就來了:理論上看,執行java -jar命令時JarLauncher類會被執行,但實際上是com.artisan.spring.Application被執行了,這其中發生了什么呢?why?
打包插件
事實上,Java沒有提供任何標準的方式來加載嵌套的jar文件 (jar中包含jar ,即Spring Boot 中的fat jar)
Spring Boot 默認的打包插件如下:
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>執行maven clean package之后,會生成兩個文件,剛才我們也看到了
spring-boot-maven-plugin簡介
spring-boot-maven-plugin項目存在于spring-boot-tools目錄中。
spring-boot-maven-plugin默認有5個goals:repackage、run、start、stop、build-info。在打包的時候默認使用的是repackage。
spring-boot-maven-plugin的repackage能夠將mvn package生成的軟件包,再次打包為可執行的軟件包,并將mvn package生成的軟件包重命名為.original*
spring-boot-maven-plugin的repackage在代碼層面調用了RepackageMojo的execute方法,而在該方法中又調用了repackage方法。
private void repackage() throws MojoExecutionException {// maven生成的jar,最終的命名將加上.original后綴Artifact source = getSourceArtifact();// 最終為可執行jar,即fat jarFile target = getTargetFile();// 獲取重新打包器,將maven生成的jar重新打包成可執行jarRepackager repackager = getRepackager(source.getFile());// 查找并過濾項目運行時依賴的jarSet<Artifact> artifacts = filterDependencies(this.project.getArtifacts(),getFilters(getAdditionalFilters()));// 將artifacts轉換成librariesLibraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack,getLog());try {// 獲得Spring Boot啟動腳本LaunchScript launchScript = getLaunchScript();// 執行重新打包,生成fat jarrepackager.repackage(target, libraries, launchScript);}catch (IOException ex) {throw new MojoExecutionException(ex.getMessage(), ex);}// 將maven生成的jar更新成.original文件updateArtifact(source, target, repackager.getBackupFile()); }執行以上命令之后,便生成了打包結果對應的兩個文件。
包結構
下面針對文件的內容和結構進行一探究竟。
spring-0.0.1-SNAPSHOT.jar ├── META-INF │ └── maven(主要是pom文件) │ └── MANIFEST.MF ├── BOOT-INF │ ├── classes │ │ └── 應用程序類 │ └── lib │ └── 第三方依賴jar └── org└── springframework└── boot└── loader└── springboot啟動程序META-INF內容
Manifest-Version: 1.0 Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx Implementation-Title: spring Implementation-Version: 0.0.1-SNAPSHOT Spring-Boot-Layers-Index: BOOT-INF/layers.idx Start-Class: com.artisan.spring.Application Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Build-Jdk-Spec: 1.8 Spring-Boot-Version: 2.4.1 Created-By: Maven Jar Plugin 3.2.0 Main-Class: org.springframework.boot.loader.JarLauncher- Main-Class:org.springframework.boot.loader.JarLauncher ,這個是jar啟動的Main函數
- Start-Class: com.artisan.spring.Application,這個是我們應用自己的Main函數
Archive的概念
在繼續了解底層概念和原理之前,我們先來了解一下Archive的概念:
- archive即歸檔文件,這個概念在linux下比較常見
- 通常就是一個tar/zip格式的壓縮包
- jar是zip格式
SpringBoot抽象了Archive的概念,一個Archive可以是jar(JarFileArchive),可以是一個文件目錄(ExplodedArchive),可以抽象為統一訪問資源的邏輯層
關于Spring Boot中Archive的源碼如下:
public interface Archive extends Iterable<Archive.Entry> {// 獲取該歸檔的urlURL getUrl() throws MalformedURLException;// 獲取jar!/META-INF/MANIFEST.MF或[ArchiveDir]/META-INF/MANIFEST.MFManifest getManifest() throws IOException;// 獲取jar!/BOOT-INF/lib/*.jar或[ArchiveDir]/BOOT-INF/lib/*.jarList<Archive> getNestedArchives(EntryFilter filter) throws IOException; }SpringBoot定義了一個接口用于描述資源,也就是org.springframework.boot.loader.archive.Archive。
該接口有兩個實現,分別是
-
org.springframework.boot.loader.archive.ExplodedArchive
-
org.springframework.boot.loader.archive.JarFileArchive。
前者用于在文件夾目錄下尋找資源,后者用于在jar包環境下尋找資源。而在SpringBoot打包的fatJar中,則是使用后者JarFileArchive
JarFile
JarFile:對jar包的封裝,每個JarFileArchive都會對應一個JarFile。
JarFile被構造的時候會解析內部結構,去獲取jar包里的各個文件或文件夾,這些文件或文件夾會被封裝到Entry中,也存儲在JarFileArchive中。如果Entry是個jar,會解析成JarFileArchive。
比如一個JarFileArchive對應的URL為:
jar:file:/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/它對應的JarFile為:
/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar這個JarFile有很多Entry,比如:
META-INF/ META-INF/MANIFEST.MF spring/ spring/study/ .... spring/study/executablejar/ExecutableJarApplication.class lib/spring-boot-starter-1.3.5.RELEASE.jar lib/spring-boot-1.3.5.RELEASE.jar ...JarFileArchive內部的一些依賴jar對應的URL(SpringBoot使用org.springframework.boot.loader.jar.Handler處理器來處理這些URL):
jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-starter-web-1.3.5.RELEASE.jar!/ jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/org/springframework/boot/loader/JarLauncher.class我們看到如果有jar包中包含jar,或者jar包中包含jar包里面的class文件,那么會使用 !/ 分隔開,這種方式只有org.springframework.boot.loader.jar.Handler能處理,它是SpringBoot內部擴展出來的一種URL協議。
JarLauncher工作流程
從MANIFEST.MF可以看到Main函數是JarLauncher,下面來分析它的工作流程。JarLauncher類的繼承結構是:
class JarLauncher extends ExecutableArchiveLauncher class ExecutableArchiveLauncher extends LauncherLauncher for JAR based archives. This launcher assumes that dependency jars are included inside a /BOOT-INF/lib directory and that application classes are included inside a /BOOT-INF/classes directory.
什么意思呢?
按照定義,JarLauncher可以加載內部/BOOT-INF/lib下的jar及/BOOT-INF/classes下的應用class。
public class JarLauncher extends ExecutableArchiveLauncher {public JarLauncher() {}public static void main(String[] args) throws Exception {new JarLauncher().launch(args);} }其主入口新建了JarLauncher并調用父類Launcher中的launch方法啟動程序。在創建JarLauncher時,父類ExecutableArchiveLauncher找到自己所在的jar,并創建archive。
JarLauncher繼承于org.springframework.boot.loader.ExecutableArchiveLauncher。該類的無參構造方法最主要的功能就是構建了當前main方法所在的FatJar的JarFileArchive對象。
下面來看launch方法。該方法主要是做了2個事情:
- (1)以FatJar為file作為入參,構造JarFileArchive對象。獲取其中所有的資源目標,取得其Url,將這些URL作為參數,構建了一個URLClassLoader。
- (2)以第一步構建的ClassLoader加載MANIFEST.MF文件中Start-Class指向的業務類,并且執行靜態方法main。進而啟動整個程序。
在Launcher的launch方法中,通過以上archive的getNestedArchives方法找到/BOOT-INF/lib下所有jar及/BOOT-INF/classes目錄所對應的archive,通過這些archives的url生成LaunchedURLClassLoader,并將其設置為線程上下文類加載器,啟動應用。
至此,才執行我們應用程序主入口類的main方法,所有應用程序類文件均可通過/BOOT-INF/classes加載,所有依賴的第三方jar均可通過/BOOT-INF/lib加載。
小結
- JarLauncher通過加載BOOT-INF/classes目錄及BOOT-INF/lib目錄下jar文件,實現了fat jar的啟動。
- SpringBoot通過擴展JarFile、JarURLConnection及URLStreamHandler,實現了jar in jar中資源的加載。
- SpringBoot通過擴展URLClassLoader–LauncherURLClassLoader,實現了jar in jar中class文件的加載。
- WarLauncher通過加載WEB-INF/classes目錄及WEB-INF/lib和WEB-INF/lib-provided目錄下的jar文件,實現了war文件的直接啟動及web容器中的啟動。
通過spring-boot-plugin 生成了MANIFEST.MF , main-class 指定運行java -jar的主程序把依賴的jar文件 打包在fat jar.
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的SpringBoot - 探究Spring Boot应用是如何通过java -jar 启动的的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot - 手把手教小师
- 下一篇: SpringBoot - 构建监控体系0