為什么SpringBoot的 jar 可以直接運(yùn)行?

為什么SpringBoot的 jar 可以直接運(yùn)行技健?

SpringBoot提供了一個(gè)插件spring-boot-maven-plugin用于把程序打包成一個(gè)可執(zhí)行的jar包写穴。在pom文件里加入這個(gè)插件即可:

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

打包完生成的executable-jar-1.0-SNAPSHOT.jar內(nèi)部的結(jié)構(gòu)如下:

├── META-INF│ ??├── MANIFEST.MF│ ??└── maven│ ??????└── spring.study│ ??????????└── executable-jar│ ??????????????├── pom.properties│ ??????????????└── pom.xml├── lib│ ??├── aopalliance-1.0.jar│ ??├── classmate-1.1.0.jar│ ??├── spring-boot-1.3.5.RELEASE.jar│ ??├── spring-boot-autoconfigure-1.3.5.RELEASE.jar│ ??├── ...├── org│ ??└── springframework│ ??????└── boot│ ??????????└── loader│ ??????????????├── ExecutableArchiveLauncher$1.class│ ??????????????├── ...└── spring????└── study????????└── executablejar????????????└── ExecutableJarApplication.class

然后可以直接執(zhí)行jar包就能啟動程序了:

java -jar executable-jar-1.0-SNAPSHOT.jar

打包出來fat jar內(nèi)部有4種文件類型:

[if !supportLists]·?[endif]META-INF文件夾:程序入口,其中MANIFEST.MF用于描述jar包的信息

[if !supportLists]·?[endif]lib目錄:放置第三方依賴的jar包凫乖,比如springboot的一些jar包

[if !supportLists]·?[endif]spring boot loader相關(guān)的代碼

[if !supportLists]·?[endif]模塊自身的代碼

MANIFEST.MF文件的內(nèi)容:

Manifest-Version: 1.0Implementation-Title: executable-jarImplementation-Version: 1.0-SNAPSHOTArchiver-Version: Plexus ArchiverBuilt-By: FormatStart-Class: spring.study.executablejar.ExecutableJarApplicationImplementation-Vendor-Id: spring.studySpring-Boot-Version: 1.3.5.RELEASECreated-By: Apache Maven 3.2.3Build-Jdk: 1.8.0_20Implementation-Vendor: Pivotal Software, Inc.Main-Class: org.springframework.boot.loader.JarLauncher

我們看到确垫,它的Main-Class是org.springframework.boot.loader.JarLauncher弓颈,當(dāng)我們使用java -jar執(zhí)行jar包的時(shí)候會調(diào)用JarLauncher的main方法帽芽,而不是我們編寫的SpringApplication。

那么JarLauncher這個(gè)類是的作用是什么的翔冀?

它是SpringBoot內(nèi)部提供的工具Spring Boot Loader提供的一個(gè)用于執(zhí)行Application類的工具類(fat jar內(nèi)部有spring loader相關(guān)的代碼就是因?yàn)檫@里用到了)导街。相當(dāng)于Spring Boot Loader提供了一套標(biāo)準(zhǔn)用于執(zhí)行SpringBoot打包出來的jar

Spring Boot Loader抽象的一些類

抽象類Launcher:各種Launcher的基礎(chǔ)抽象類,用于啟動應(yīng)用程序纤子;跟Archive配合使用搬瑰;目前有3種實(shí)現(xiàn),分別是JarLauncher控硼、WarLauncher以及PropertiesLauncher

Archive:歸檔文件的基礎(chǔ)抽象類泽论。JarFileArchive就是jar包文件的抽象。它提供了一些方法比如getUrl會返回這個(gè)Archive對應(yīng)的URL卡乾;getManifest方法會獲得Manifest數(shù)據(jù)等翼悴。ExplodedArchive是文件目錄的抽象

JarFile:對jar包的封裝,每個(gè)JarFileArchive都會對應(yīng)一個(gè)JarFile幔妨。JarFile被構(gòu)造的時(shí)候會解析內(nèi)部結(jié)構(gòu)鹦赎,去獲取jar包里的各個(gè)文件或文件夾谍椅,這些文件或文件夾會被封裝到Entry中,也存儲在JarFileArchive中古话。如果Entry是個(gè)jar雏吭,會解析成JarFileArchive。

比如一個(gè)JarFileArchive對應(yīng)的URL為:

jar:file:/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/

它對應(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.MFspring/spring/study/....spring/study/executablejar/ExecutableJarApplication.classlib/spring-boot-starter-1.3.5.RELEASE.jarlib/spring-boot-1.3.5.RELEASE.jar...

JarFileArchive內(nèi)部的一些依賴jar對應(yīng)的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內(nèi)部擴(kuò)展出來的一種URL協(xié)議。

JarLauncher的執(zhí)行過程

JarLauncher的main方法:

public?static?void?main(String[]?args)?{????//?構(gòu)造JarLauncher婚温,然后調(diào)用它的launch方法描焰。參數(shù)是控制臺傳遞的????new?JarLauncher().launch(args);}??

JarLauncher被構(gòu)造的時(shí)候會調(diào)用父類ExecutableArchiveLauncher的構(gòu)造方法。

ExecutableArchiveLauncher的構(gòu)造方法內(nèi)部會去構(gòu)造Archive栅螟,這里構(gòu)造了JarFileArchive荆秦。構(gòu)造JarFileArchive的過程中還會構(gòu)造很多東西,比如JarFile力图,Entry …

JarLauncher的launch方法:protected?void?launch(String[]?args)?{??try?{????//?在系統(tǒng)屬性中設(shè)置注冊了自定義的URL處理器:org.springframework.boot.loader.jar.Handler步绸。如果URL中沒有指定處理器,會去系統(tǒng)屬性中查詢????JarFile.registerUrlProtocolHandler();????//?getClassPathArchives方法在會去找lib目錄下對應(yīng)的第三方依賴JarFileArchive吃媒,同時(shí)也會項(xiàng)目自身的JarFileArchive????//?根據(jù)getClassPathArchives得到的JarFileArchive集合去創(chuàng)建類加載器ClassLoader瓤介。這里會構(gòu)造一個(gè)LaunchedURLClassLoader類加載器,這個(gè)類加載器繼承URLClassLoader赘那,并使用這些JarFileArchive集合的URL構(gòu)造成URLClassPath????//?LaunchedURLClassLoader類加載器的父類加載器是當(dāng)前執(zhí)行類JarLauncher的類加載器????ClassLoader?classLoader?=?createClassLoader(getClassPathArchives());????//?getMainClass方法會去項(xiàng)目自身的Archive中的Manifest中找出key為Start-Class的類????//?調(diào)用重載方法launch????launch(args,?getMainClass(),?classLoader);??}??catch?(Exception?ex)?{????ex.printStackTrace();????System.exit(1);??}}//?Archive的getMainClass方法//?這里會找出spring.study.executablejar.ExecutableJarApplication這個(gè)類public?String?getMainClass()?throws?Exception?{??Manifest?manifest?=?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);??}??return?mainClass;}//?launch重載方法protected?void?launch(String[]?args,?String?mainClass,?ClassLoader?classLoader)????throws?Exception?{??????//?創(chuàng)建一個(gè)MainMethodRunner刑桑,并把a(bǔ)rgs和Start-Class傳遞給它??Runnable?runner?=?createMainMethodRunner(mainClass,?args,?classLoader);??????//?構(gòu)造新線程??Thread?runnerThread?=?new?Thread(runner);??????//?線程設(shè)置類加載器以及名字,然后啟動??runnerThread.setContextClassLoader(classLoader);??runnerThread.setName(Thread.currentThread().getName());??runnerThread.start();}

MainMethodRunner的run方法:

@Overridepublic?void?run()?{??try?{????//?根據(jù)Start-Class進(jìn)行實(shí)例化????Class<?>?mainClass?=?Thread.currentThread().getContextClassLoader()????????.loadClass(this.mainClassName);????//?找出main方法????Method?mainMethod?=?mainClass.getDeclaredMethod("main",?String[].class);????//?如果main方法不存在募舟,拋出異常????if?(mainMethod?==?null)?{??????throw?new?IllegalStateException(??????????this.mainClassName?+?"?does?not?have?a?main?method");????}????//?調(diào)用????mainMethod.invoke(null,?new?Object[]?{?this.args?});??}??catch?(Exception?ex)?{????UncaughtExceptionHandler?handler?=?Thread.currentThread()????????.getUncaughtExceptionHandler();????if?(handler?!=?null)?{??????handler.uncaughtException(Thread.currentThread(),?ex);????}????throw?new?RuntimeException(ex);??}}

Start-Class的main方法調(diào)用之后祠斧,內(nèi)部會構(gòu)造Spring容器,啟動內(nèi)置Servlet容器等過程拱礁。這些過程我們都已經(jīng)分析過了琢锋。

關(guān)于自定義的類加載器LaunchedURLClassLoader

LaunchedURLClassLoader重寫了loadClass方法,也就是說它修改了默認(rèn)的類加載方式(先看該類是否已加載這部分不變呢灶,后面真正去加載類的規(guī)則改變了吴超,不再是直接從父類加載器中去加載)。LaunchedURLClassLoader定義了自己的類加載規(guī)則:

private?Class<?>?doLoadClass(String?name)?throws?ClassNotFoundException?{??//?1)?Try?the?root?class?loader??try?{????if?(this.rootClassLoader?!=?null)?{??????return?this.rootClassLoader.loadClass(name);????}??}??catch?(Exception?ex)?{????//?Ignore?and?continue??}??//?2)?Try?to?find?locally??try?{????findPackage(name);????Class<?>?cls?=?findClass(name);????return?cls;??}??catch?(Exception?ex)?{????//?Ignore?and?continue??}??//?3)?Use?standard?loading??return?super.loadClass(name,?false);}

加載規(guī)則:

[if !supportLists]·?[endif]如果根類加載器存在鸯乃,調(diào)用它的加載方法鲸阻。這里是根類加載是ExtClassLoader

[if !supportLists]·?[endif]調(diào)用LaunchedURLClassLoader自身的findClass方法,也就是URLClassLoader的findClass方法

[if !supportLists]·?[endif]調(diào)用父類的loadClass方法,也就是執(zhí)行默認(rèn)的類加載順序(從BootstrapClassLoader開始從下往下尋找)

LaunchedURLClassLoader自身的findClass方法:

protected?Class<?>?findClass(final?String?name)?????throws?ClassNotFoundException{????try?{????????return?AccessController.doPrivileged(????????????new?PrivilegedExceptionAction<Class<?>>()?{????????????????public?Class<?>?run()?throws?ClassNotFoundException?{????????????????????//?把類名解析成路徑并加上.class后綴????????????????????String?path?=?name.replace('.',?'/').concat(".class");????????????????????//?基于之前得到的第三方j(luò)ar包依賴以及自己的jar包得到URL數(shù)組赘娄,進(jìn)行遍歷找出對應(yīng)類名的資源????????????????????//?比如path是org/springframework/boot/loader/JarLauncher.class仆潮,它在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!/中被找出????????????????????//?那么找出的資源對應(yīng)的URL為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????????????????????Resource?res?=?ucp.getResource(path,?false);????????????????????if?(res?!=?null)?{?//?找到了資源????????????????????????try?{????????????????????????????return?defineClass(name,?res);????????????????????????}?catch?(IOException?e)?{????????????????????????????throw?new?ClassNotFoundException(name,?e);????????????????????????}????????????????????}?else?{?//?找不到資源的話直接拋出ClassNotFoundException異常????????????????????????throw?new?ClassNotFoundException(name);????????????????????}????????????????}????????????},?acc);????}?catch?(java.security.PrivilegedActionException?pae)?{????????throw?(ClassNotFoundException)?pae.getException();????}}

下面是LaunchedURLClassLoader的一個(gè)測試:

//?注冊org.springframework.boot.loader.jar.Handler?URL協(xié)議處理器JarFile.registerUrlProtocolHandler();//?構(gòu)造LaunchedURLClassLoader類加載器,這里使用了2個(gè)URL遣臼,分別對應(yīng)jar包中依賴包spring-boot-loader和spring-boot性置,使用?"!/"?分開,需要org.springframework.boot.loader.jar.Handler處理器處理LaunchedURLClassLoader?classLoader?=?new?LaunchedURLClassLoader(????????new?URL[]?{????????????????new?URL("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!/")????????????????,?new?URL("jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-1.3.5.RELEASE.jar!/")????????},????????LaunchedURLClassLoaderTest.class.getClassLoader());//?加載類//?這2個(gè)類都會在第二步本地查找中被找出(URLClassLoader的findClass方法)classLoader.loadClass("org.springframework.boot.loader.JarLauncher");classLoader.loadClass("org.springframework.boot.SpringApplication");//?在第三步使用默認(rèn)的加載順序在ApplicationClassLoader中被找出classLoader.loadClass("org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration");

Spring Boot Loader的作用

SpringBoot在可執(zhí)行jar包中定義了自己的一套規(guī)則揍堰,比如第三方依賴jar包在/lib目錄下鹏浅,jar包的URL路徑使用自定義的規(guī)則并且這個(gè)規(guī)則需要使用org.springframework.boot.loader.jar.Handler處理器處理。它的Main-Class使用JarLauncher屏歹,如果是war包隐砸,使用WarLauncher執(zhí)行。這些Launcher內(nèi)部都會另起一個(gè)線程啟動自定義的SpringApplication類蝙眶。

這些特性通過spring-boot-maven-plugin插件打包完成季希。

所謂技多不壓身,我們所讀過的每一本書幽纷,所學(xué)過的每一門語言式塌,在未來指不定都能給我們意想不到的回饋呢。其實(shí)做為一個(gè)開發(fā)者友浸,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要這里我推薦一個(gè)Java學(xué)習(xí)交流群342016322峰尝,不管你是小白還是大牛歡迎入駐,大家一起交流成長收恢。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末武学,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子伦意,更是在濱河造成了極大的恐慌火窒,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件默赂,死亡現(xiàn)場離奇詭異沛鸵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)缆八,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疾捍,“玉大人奈辰,你說我怎么就攤上這事÷叶梗” “怎么了奖恰?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我瑟啃,道長论泛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任蛹屿,我火速辦了婚禮屁奏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘错负。我一直安慰自己坟瓢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布犹撒。 她就那樣靜靜地躺著折联,像睡著了一般。 火紅的嫁衣襯著肌膚如雪识颊。 梳的紋絲不亂的頭發(fā)上诚镰,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機(jī)與錄音祥款,去河邊找鬼怕享。 笑死,一個(gè)胖子當(dāng)著我的面吹牛镰踏,可吹牛的內(nèi)容都是我干的函筋。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼奠伪,長吁一口氣:“原來是場噩夢啊……” “哼跌帐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起绊率,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤谨敛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后滤否,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脸狸,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年藐俺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了炊甲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡欲芹,死狀恐怖卿啡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情菱父,我是刑警寧澤颈娜,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布剑逃,位于F島的核電站,受9級特大地震影響官辽,放射性物質(zhì)發(fā)生泄漏蛹磺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一同仆、第九天 我趴在偏房一處隱蔽的房頂上張望萤捆。 院中可真熱鬧,春花似錦乓梨、人聲如沸鳖轰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蕴侣。三九已至,卻和暖如春臭觉,著一層夾襖步出監(jiān)牢的瞬間昆雀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工蝠筑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狞膘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓什乙,卻偏偏與公主長得像挽封,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子臣镣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容