Spring Boot如何通過(guò)java -jar啟動(dòng)
Pre
大家開發(fā)的基于Spring Boot 的應(yīng)用 ,jar形式, 發(fā)布的時(shí)候,絕大部分都是使用java -jar 啟動(dòng)。 得益于Spring Boot 的封裝 , 再也不用操心搭建tomcat等相關(guān)web容器le , 一切變得非常美好, 那SpringBoot是怎么做到的呢?
引導(dǎo)
新建工程 打包 啟動(dòng)
我們新創(chuàng)建一個(gè)Spring Boot的工程
其中打包的配置為
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
先打包一下
查看target目錄
然后啟動(dòng)
java -jar 干啥的
我們先看看 java -jar 干了啥 ?
在oracle官網(wǎng)找到了該命令的描述:
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參數(shù)時(shí),后面的參數(shù)是的jar 【spring-0.0.1-SNAPSHOT.jar】,該jar文件中包含的是class和資源文件; 在manifest文件中有Main-Class的定義;Main-Class的源碼中指定了整個(gè)應(yīng)用的啟動(dòng)類;
簡(jiǎn)單來(lái)說(shuō): java -jar會(huì)去找jar中的manifest文件,去找到Main-Class對(duì)應(yīng)的真正的啟動(dòng)類;
那看看去吧
咦 ,這個(gè)Main-Class 是Spring Boot 的。
我們還看到有個(gè)Start Class
官方文檔中,只提到過(guò)Main-Class ,并沒(méi)有提到Start-Class;
Start-Class的值是com.artisan.spring.Application
,這是我們的java代碼中的唯一類,包含main方法, 是能夠真正的應(yīng)用啟動(dòng)類
所以問(wèn)題就來(lái)了:理論上看,執(zhí)行java -jar命令時(shí)JarLauncher類會(huì)被執(zhí)行,但實(shí)際上是com.artisan.spring.Application
被執(zhí)行了,這其中發(fā)生了什么呢?why?
打包插件
事實(shí)上,Java沒(méi)有提供任何標(biāo)準(zhǔn)的方式來(lái)加載嵌套的jar文件 (jar中包含jar ,即Spring Boot 中的fat jar)
Spring Boot 默認(rèn)的打包插件如下:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
執(zhí)行maven clean package之后,會(huì)生成兩個(gè)文件,剛才我們也看到了
spring-boot-maven-plugin簡(jiǎn)介
spring-boot-maven-plugin項(xiàng)目存在于spring-boot-tools目錄中。
spring-boot-maven-plugin默認(rèn)有5個(gè)goals:repackage、run、start、stop、build-info。在打包的時(shí)候默認(rèn)使用的是repackage。
spring-boot-maven-plugin的repackage能夠?qū)vn package生成的軟件包,再次打包為可執(zhí)行的軟件包,并將mvn package生成的軟件包重命名為.original*
spring-boot-maven-plugin的repackage在代碼層面調(diào)用了RepackageMojo
的execute
方法,而在該方法中又調(diào)用了repackage方法。
private void repackage() throws MojoExecutionException { // maven生成的jar,最終的命名將加上.original后綴 Artifact source = getSourceArtifact(); // 最終為可執(zhí)行jar,即fat jar File target = getTargetFile(); // 獲取重新打包器,將maven生成的jar重新打包成可執(zhí)行jar Repackager repackager = getRepackager(source.getFile()); // 查找并過(guò)濾項(xiàng)目運(yùn)行時(shí)依賴的jar Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(), getFilters(getAdditionalFilters())); // 將artifacts轉(zhuǎn)換成libraries Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack, getLog()); try { // 獲得Spring Boot啟動(dòng)腳本 LaunchScript launchScript = getLaunchScript(); // 執(zhí)行重新打包,生成fat jar repackager.repackage(target, libraries, launchScript); }catch (IOException ex) { throw new MojoExecutionException(ex.getMessage(), ex); } // 將maven生成的jar更新成.original文件 updateArtifact(source, target, repackager.getBackupFile()); }
執(zhí)行以上命令之后,便生成了打包結(jié)果對(duì)應(yīng)的兩個(gè)文件。
包結(jié)構(gòu)
下面針對(duì)文件的內(nèi)容和結(jié)構(gòu)進(jìn)行一探究竟。
spring-0.0.1-SNAPSHOT.jar ├── META-INF │ └── maven(主要是pom文件) │ └── MANIFEST.MF ├── BOOT-INF │ ├── classes │ │ └── 應(yīng)用程序類 │ └── lib │ └── 第三方依賴jar └── org └── springframework └── boot └── loader └── springboot啟動(dòng)程序
META-INF內(nèi)容
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 ,這個(gè)是jar啟動(dòng)的Main函數(shù)Start-Class: com.artisan.spring.Application,這個(gè)是我們應(yīng)用自己的Main函數(shù)
Archive的概念
在繼續(xù)了解底層概念和原理之前,我們先來(lái)了解一下Archive的概念:
- archive即歸檔文件,這個(gè)概念在linux下比較常見(jiàn)
- 通常就是一個(gè)tar/zip格式的壓縮包
- jar是zip格式
SpringBoot抽象了Archive的概念,一個(gè)Archive可以是jar(JarFileArchive),可以是一個(gè)文件目錄(ExplodedArchive),可以抽象為統(tǒng)一訪問(wèn)資源的邏輯層
關(guān)于Spring Boot中Archive的源碼如下:
public interface Archive extends Iterable<Archive.Entry> { // 獲取該歸檔的url URL getUrl() throws MalformedURLException; // 獲取jar!/META-INF/MANIFEST.MF或[ArchiveDir]/META-INF/MANIFEST.MF Manifest getManifest() throws IOException; // 獲取jar!/BOOT-INF/lib/*.jar或[ArchiveDir]/BOOT-INF/lib/*.jar List<Archive> getNestedArchives(EntryFilter filter) throws IOException; }
SpringBoot定義了一個(gè)接口用于描述資源,也就是org.springframework.boot.loader.archive.Archive。
該接口有兩個(gè)實(shí)現(xiàn),分別是
- org.springframework.boot.loader.archive.ExplodedArchive
- org.springframework.boot.loader.archive.JarFileArchive。
前者用于在文件夾目錄下尋找資源,后者用于在jar包環(huán)境下尋找資源。而在SpringBoot打包的fatJar中,則是使用后者JarFileArchive
JarFile
JarFile:對(duì)jar包的封裝,每個(gè)JarFileArchive都會(huì)對(duì)應(yīng)一個(gè)JarFile。
JarFile被構(gòu)造的時(shí)候會(huì)解析內(nèi)部結(jié)構(gòu),去獲取jar包里的各個(gè)文件或文件夾,這些文件或文件夾會(huì)被封裝到Entry中,也存儲(chǔ)在JarFileArchive中。如果Entry是個(gè)jar,會(huì)解析成JarFileArchive。
比如一個(gè)JarFileArchive對(duì)應(yīng)的URL為:
jar:file:/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/
它對(duì)應(yīng)的JarFile為:
/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar
這個(gè)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內(nèi)部的一些依賴jar對(duì)應(yīng)的URL(SpringBoot使用org.springframework.boot.loader.jar.Handler
處理器來(lái)處理這些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文件,那么會(huì)使用 !/ 分隔開,這種方式只有org.springframework.boot.loader.jar.Handler
能處理,它是SpringBoot內(nèi)部擴(kuò)展出來(lái)的一種URL協(xié)議。
JarLauncher工作流程
從MANIFEST.MF可以看到Main函數(shù)是JarLauncher,下面來(lái)分析它的工作流程。JarLauncher類的繼承結(jié)構(gòu)是:
class JarLauncher extends ExecutableArchiveLauncher class ExecutableArchiveLauncher extends Launcher
Launcher 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可以加載內(nèi)部/BOOT-INF/lib下的jar及/BOOT-INF/classes下的應(yīng)用class。
public class JarLauncher extends ExecutableArchiveLauncher { public JarLauncher() {} public static void main(String[] args) throws Exception { new JarLauncher().launch(args); } }
其主入口新建了JarLauncher并調(diào)用父類Launcher中的launch方法啟動(dòng)程序。在創(chuàng)建JarLauncher時(shí),父類ExecutableArchiveLauncher找到自己所在的jar,并創(chuàng)建archive。
JarLauncher繼承于org.springframework.boot.loader.ExecutableArchiveLauncher。該類的無(wú)參構(gòu)造方法最主要的功能就是構(gòu)建了當(dāng)前main方法所在的FatJar的JarFileArchive對(duì)象。
下面來(lái)看launch方法。該方法主要是做了2個(gè)事情:
(1)以FatJar為file作為入?yún)?,?gòu)造JarFileArchive對(duì)象。獲取其中所有的資源目標(biāo),取得其Url,將這些URL作為參數(shù),構(gòu)建了一個(gè)URLClassLoader。
(2)以第一步構(gòu)建的ClassLoader加載MANIFEST.MF文件中Start-Class指向的業(yè)務(wù)類,并且執(zhí)行靜態(tài)方法main。進(jìn)而啟動(dòng)整個(gè)程序。
public abstract class ExecutableArchiveLauncher extends Launcher { private final Archive archive; public ExecutableArchiveLauncher() { try { // 找到自己所在的jar,并創(chuàng)建Archive this.archive = createArchive(); } catch (Exception ex) { throw new IllegalStateException(ex); } } } public abstract class Launcher { protected final Archive createArchive() throws Exception { ProtectionDomain protectionDomain = getClass().getProtectionDomain(); CodeSource codeSource = protectionDomain.getCodeSource(); URI location = (codeSource == null ? null : codeSource.getLocation().toURI()); String path = (location == null ? null : location.getSchemeSpecificPart()); if (path == null) { throw new IllegalStateException("Unable to determine code source archive"); } File root = new File(path); if (!root.exists()) { throw new IllegalStateException( "Unable to determine code source archive from " + root); } return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root)); } }
在Launcher的launch方法中,通過(guò)以上archive的getNestedArchives方法找到/BOOT-INF/lib下所有jar及/BOOT-INF/classes目錄所對(duì)應(yīng)的archive,通過(guò)這些archives的url生成LaunchedURLClassLoader
,并將其設(shè)置為線程上下文類加載器,啟動(dòng)應(yīng)用。
至此,才執(zhí)行我們應(yīng)用程序主入口類的main方法,所有應(yīng)用程序類文件均可通過(guò)/BOOT-INF/classes加載,所有依賴的第三方j(luò)ar均可通過(guò)/BOOT-INF/lib加載。
小結(jié)
- JarLauncher通過(guò)加載BOOT-INF/classes目錄及BOOT-INF/lib目錄下jar文件,實(shí)現(xiàn)了fat jar的啟動(dòng)。
- SpringBoot通過(guò)擴(kuò)展JarFile、JarURLConnection及URLStreamHandler,實(shí)現(xiàn)了jar in jar中資源的加載。
- SpringBoot通過(guò)擴(kuò)展URLClassLoader–LauncherURLClassLoader,實(shí)現(xiàn)了jar in jar中class文件的加載。
- WarLauncher通過(guò)加載WEB-INF/classes目錄及WEB-INF/lib和WEB-INF/lib-provided目錄下的jar文件,實(shí)現(xiàn)了war文件的直接啟動(dòng)及web容器中的啟動(dòng)。
通過(guò)spring-boot-plugin 生成了MANIFEST.MF , main-class 指定運(yùn)行java -jar的主程序把依賴的jar文件 打包在fat jar.
到此這篇關(guān)于Spring Boot如何通過(guò)java -jar啟動(dòng)的文章就介紹到這了,更多相關(guān)SpringBoot java -jar啟動(dòng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- java?-jar命令及SpringBoot通過(guò)java?-jav啟動(dòng)項(xiàng)目的過(guò)程
- SpringBoot java-jar命令行啟動(dòng)原理解析
- Spring Boot 的java -jar命令啟動(dòng)原理詳解
- SpringBoot工程打包后執(zhí)行Java?-Jar就能啟動(dòng)的步驟原理
- SpringBoot應(yīng)用能直接運(yùn)行java -jar的原因分析
- SpringBoot的java -jar命令啟動(dòng)原理解讀
- Spring Boot項(xiàng)目部署命令java -jar的各種參數(shù)及作用詳解
相關(guān)文章
Springboot整合mybatis開啟二級(jí)緩存的實(shí)現(xiàn)示例
在一級(jí)緩存中,是查詢兩次數(shù)據(jù)庫(kù)的,顯然這是一種浪費(fèi),既然SQL查詢相同,就沒(méi)有必要再次查庫(kù)了,直接利用緩存數(shù)據(jù)即可,這種思想就是MyBatis二級(jí)緩存的初衷,本文就詳細(xì)的介紹了Springboot整合mybatis開啟二級(jí)緩存,感興趣的可以了解一下2022-05-05Java線上CPU內(nèi)存沖高問(wèn)題排查解決步驟
這篇文章主要介紹了Java線上CPU內(nèi)存沖高問(wèn)題排查解決步驟的相關(guān)資料,Java程序在實(shí)際生產(chǎn)過(guò)程中經(jīng)常遇到CPU或內(nèi)存使用率高的問(wèn)題,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07Spring注解@Qualifier的詳細(xì)用法你知道幾種
本文給大家分享Spring注解@Qualifier的詳細(xì)用法,包括@Autowired和@Resource區(qū)別介紹,本文通過(guò)示例代碼給大家詳細(xì)介紹,感興趣的朋友跟隨小編一起看看吧2021-07-07JDK動(dòng)態(tài)代理過(guò)程原理及手寫實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了JDK動(dòng)態(tài)代理過(guò)程原理及手寫實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Java實(shí)現(xiàn)經(jīng)典捕魚達(dá)人游戲的示例代碼
《捕魚達(dá)人》是一款以深海狩獵為題材的休閑競(jìng)技游戲。本文將利用Java實(shí)現(xiàn)這一經(jīng)典的游戲,文中采用了swing技術(shù)進(jìn)行了界面化處理,需要的可以參考一下2022-02-02Http Cookie機(jī)制及Cookie的實(shí)現(xiàn)原理
Cookie是進(jìn)行網(wǎng)站用戶身份,實(shí)現(xiàn)服務(wù)端Session會(huì)話持久化的一種非常好方式。Cookie最早由Netscape公司開發(fā),現(xiàn)在由 IETF 的RFC 6265標(biāo)準(zhǔn)備對(duì)其規(guī)范,已被所有主流瀏覽器所支持2021-06-06