javascript
boot spring 怎么执行hql_彻底透析SpringBoot jar可执行原理
點(diǎn)擊上方“方志朋”,選擇“設(shè)為星標(biāo)”
做積極的人,而不是積極廢人
作者:plz叫我紅領(lǐng)巾
juejin.im/post/5d2d6812e51d45777b1a3e5a
文章篇幅較長,但是包含了SpringBoot 可執(zhí)行jar包從頭到尾的原理,請(qǐng)讀者耐心觀看。
同時(shí)文章是基于SpringBoot-2.1.3進(jìn)行分析。涉及的知識(shí)點(diǎn)主要包括Maven的生命周期以及自定義插件,JDK提供關(guān)于jar包的工具類以及Springboot如何擴(kuò)展,最后是自定義類加載器。
spring-boot-maven-plugin
SpringBoot 的可執(zhí)行jar包又稱fat jar ,是包含所有第三方依賴的 jar 包,jar 包中嵌入了除 java 虛擬機(jī)以外的所有依賴,是一個(gè) all-in-one jar 包。
普通插件maven-jar-plugin生成的包和spring-boot-maven-plugin生成的包之間的直接區(qū)別,是fat jar中主要增加了兩部分,第一部分是lib目錄,存放的是Maven依賴的jar包文件,第二部分是spring boot loader相關(guān)的類。
fat?jar?//目錄結(jié)構(gòu)├─BOOT-INF
│??├─classes
│??└─lib
├─META-INF
│??├─maven
│??├─app.properties
│??├─MANIFEST.MF??????
└─org
????└─springframework
????????└─boot
????????????└─loader
????????????????├─archive
????????????????├─data
????????????????├─jar
????????????????└─util
也就是說想要知道fat jar是如何生成的,就必須知道spring-boot-maven-plugin工作機(jī)制,而spring-boot-maven-plugin屬于自定義插件,因此我們又必須知道,Maven的自定義插件是如何工作的
Maven的自定義插件
Maven 擁有三套相互獨(dú)立的生命周期: clean、default 和 site, 而每個(gè)生命周期包含一些phase階段, 階段是有順序的, 并且后面的階段依賴于前面的階段。生命周期的階段phase與插件的目標(biāo)goal相互綁定,用以完成實(shí)際的構(gòu)建任務(wù)。
<plugin>????<groupId>org.springframework.bootgroupId>
????<artifactId>spring-boot-maven-pluginartifactId>
????<executions>
????????<execution>
????????????<goals>
????????????????<goal>repackagegoal>
????????????goals>
????????execution>
????executions>
plugin>
repackage目標(biāo)對(duì)應(yīng)的將執(zhí)行到org.springframework.boot.maven.RepackageMojo#execute,該方法的主要邏輯是調(diào)用了org.springframework.boot.maven.RepackageMojo#repackage
private?void?repackage()?throws?MojoExecutionException?{?????//獲取使用maven-jar-plugin生成的jar,最終的命名將加上.orignal后綴
???Artifact?source?=?getSourceArtifact();
????//最終文件,即Fat?jar
???File?target?=?getTargetFile();
????//獲取重新打包器,將重新打包成可執(zhí)行jar文件
???Repackager?repackager?=?getRepackager(source.getFile());
????//查找并過濾項(xiàng)目運(yùn)行時(shí)依賴的jar
???Set?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);
???}//將source更新成?xxx.jar.orignal文件
???updateArtifact(source,?target,?repackager.getBackupFile());
}
我們關(guān)心一下org.springframework.boot.maven.RepackageMojo#getRepackager這個(gè)方法,知道Repackager是如何生成的,也就大致能夠推測出內(nèi)在的打包邏輯。
private?Repackager?getRepackager(File?source)?{???Repackager?repackager?=?new?Repackager(source,?this.layoutFactory);
???repackager.addMainClassTimeoutWarningListener(
?????????new?LoggingMainClassTimeoutWarningListener());
????//設(shè)置main?class的名稱,如果不指定的話則會(huì)查找第一個(gè)包含main方法的類,repacke最后將會(huì)設(shè)置org.springframework.boot.loader.JarLauncher
???repackager.setMainClass(this.mainClass);
???if?(this.layout?!=?null)?{
??????getLog().info("Layout:?"?+?this.layout);
???????//重點(diǎn)關(guān)心下layout?最終返回了?org.springframework.boot.loader.tools.Layouts.Jar
??????repackager.setLayout(this.layout.layout());
???}
???return?repackager;
}
/**
?*?Executable?JAR?layout.
?*/
public?static?class?Jar?implements?RepackagingLayout?{
???@Override
???public?String?getLauncherClassName()?{
??????return?"org.springframework.boot.loader.JarLauncher";
???}
???@Override
???public?String?getLibraryDestination(String?libraryName,?LibraryScope?scope)?{
??????return?"BOOT-INF/lib/";
???}
???@Override
???public?String?getClassesLocation()?{
??????return?"";
???}
???@Override
???public?String?getRepackagedClassesLocation()?{
??????return?"BOOT-INF/classes/";
???}
???@Override
???public?boolean?isExecutable()?{
??????return?true;
???}
}
layout我們可以將之翻譯為文件布局,或者目錄布局,代碼一看清晰明了,同時(shí)我們需要關(guān)注,也是下一個(gè)重點(diǎn)關(guān)注對(duì)象org.springframework.boot.loader.JarLauncher,從名字推斷,這很可能是返回可執(zhí)行jar文件的啟動(dòng)類。
MANIFEST.MF文件內(nèi)容
Manifest-Version:?1.0Implementation-Title:?oneday-auth-server
Implementation-Version:?1.0.0-SNAPSHOT
Archiver-Version:?Plexus?Archiver
Built-By:?oneday
Implementation-Vendor-Id:?com.oneday
Spring-Boot-Version:?2.1.3.RELEASE
Main-Class:?org.springframework.boot.loader.JarLauncher
Start-Class:?com.oneday.auth.Application
Spring-Boot-Classes:?BOOT-INF/classes/
Spring-Boot-Lib:?BOOT-INF/lib/
Created-By:?Apache?Maven?3.3.9
Build-Jdk:?1.8.0_171
repackager生成的MANIFEST.MF文件為以上信息,可以看到兩個(gè)關(guān)鍵信息Main-Class和Start-Class。我們可以進(jìn)一步,程序的啟動(dòng)入口并不是我們SpringBoot中定義的main,而是JarLauncher#main,而再在其中利用反射調(diào)用定義好的Start-Class的main方法
JarLauncher
重點(diǎn)類介紹
java.util.jar.JarFile JDK工具類提供的讀取jar文件
org.springframework.boot.loader.jar.JarFileSpringboot-loader 繼承JDK提供JarFile類
java.util.jar.JarEntryDK工具類提供的``jar```文件條目
org.springframework.boot.loader.jar.JarEntry Springboot-loader 繼承JDK提供JarEntry類
org.springframework.boot.loader.archive.Archive Springboot抽象出來的統(tǒng)一訪問資源的層JarFileArchivejar包文件的抽象ExplodedArchive文件目錄
這里重點(diǎn)描述一下JarFile的作用,每個(gè)JarFileArchive都會(huì)對(duì)應(yīng)一個(gè)JarFile。在構(gòu)造的時(shí)候會(huì)解析內(nèi)部結(jié)構(gòu),去獲取jar包里的各個(gè)文件或文件夾類。我們可以看一下該類的注釋。
/*?Extended?variant?of?{@link?java.util.jar.JarFile}?that?behaves?in?the?same?way?but*?offers?the?following?additional?functionality.
*?
*?A?nested?{@link?JarFile}?can?be?{@link?#getNestedJarFile(ZipEntry)?obtained}?based
*?on?any?directory?entry.
*?A?nested?{@link?JarFile}?can?be?{@link?#getNestedJarFile(ZipEntry)?obtained}?for
*?embedded?JAR?files?(as?long?as?their?entry?is?not?compressed).
**/
jar里的資源分隔符是!/,在JDK提供的JarFile URL只支持一個(gè)’!/‘,而Spring boot擴(kuò)展了這個(gè)協(xié)議,讓它支持多個(gè)’!/‘,就可以表示jar in jar、jar in directory、fat jar的資源了。
自定義類加載機(jī)制
最基礎(chǔ):Bootstrap ClassLoader(加載JDK的/lib目錄下的類)
次基礎(chǔ):Extension ClassLoader(加載JDK的/lib/ext目錄下的類)
普通:Application ClassLoader(程序自己classpath下的類)
首先需要關(guān)注雙親委派機(jī)制很重要的一點(diǎn)是,如果一個(gè)類可以被委派最基礎(chǔ)的ClassLoader加載,就不能讓高層的ClassLoader加載,這樣是為了范圍錯(cuò)誤的引入了非JDK下但是類名一樣的類。
其二,如果在這個(gè)機(jī)制下,由于fat jar中依賴的各個(gè)第三方j(luò)ar文件,并不在程序自己classpath下,也就是說,如果我們采用雙親委派機(jī)制的話,根本獲取不到我們所依賴的jar包,因此我們需要修改雙親委派機(jī)制的查找class的方法,自定義類加載機(jī)制。
先簡單的介紹Springboot2中LaunchedURLClassLoader,該類繼承了java.net.URLClassLoader,重寫了java.lang.ClassLoader#loadClass(java.lang.String, boolean),然后我們再探討他是如何修改雙親委派機(jī)制。
在上面我們講到Spring boot支持多個(gè)’!/‘以表示多個(gè)jar,而我們的問題在于,如何解決查找到這多個(gè)jar包。我們看一下LaunchedURLClassLoader的構(gòu)造方法。
public?LaunchedURLClassLoader(URL[]?urls,?ClassLoader?parent)?{???super(urls,?parent);
}
urls注釋解釋道the URLs from which to load classes and resources,即fat jar包依賴的所有類和資源,將該urls參數(shù)傳遞給父類java.net.URLClassLoader,由父類的java.net.URLClassLoader#findClass執(zhí)行查找類方法,該類的查找來源即構(gòu)造方法傳遞進(jìn)來的urls參數(shù)。
//LaunchedURLClassLoader的實(shí)現(xiàn)protected?Class>?loadClass(String?name,?boolean?resolve)
??????throws?ClassNotFoundException?{
???Handler.setUseFastConnectionExceptions(true);
???try?{
??????try?{
??????????//嘗試根據(jù)類名去定義類所在的包,即java.lang.Package,確保jar?in?jar里匹配的manifest能夠和關(guān)聯(lián)???????????????//的package關(guān)聯(lián)起來
?????????definePackageIfNecessary(name);
??????}
??????catch?(IllegalArgumentException?ex)?{
?????????//?Tolerate?race?condition?due?to?being?parallel?capable
?????????if?(getPackage(name)?==?null)?{
????????????//?This?should?never?happen?as?the?IllegalArgumentException?indicates
????????????//?that?the?package?has?already?been?defined?and,?therefore,
????????????//?getPackage(name)?should?not?return?null.
????????????//這里異常表明,definePackageIfNecessary方法的作用實(shí)際上是預(yù)先過濾掉查找不到的包
????????????throw?new?AssertionError("Package?"?+?name?+?"?has?already?been?"
??????????????????+?"defined?but?it?could?not?be?found");
?????????}
??????}
??????return?super.loadClass(name,?resolve);
???}
???finally?{
??????Handler.setUseFastConnectionExceptions(false);
???}
}
方法super.loadClass(name, resolve)實(shí)際上會(huì)回到了java.lang.ClassLoader#loadClass(java.lang.String, boolean),遵循雙親委派機(jī)制進(jìn)行查找類,而Bootstrap ClassLoader和Extension ClassLoader將會(huì)查找不到fat jar依賴的類,最終會(huì)來到Application ClassLoader,調(diào)用java.net.URLClassLoader#findClass
如何真正的啟動(dòng)
Springboot2和Springboot1的最大區(qū)別在于,Springboo1會(huì)新起一個(gè)線程,來執(zhí)行相應(yīng)的反射調(diào)用邏輯,而SpringBoot2則去掉了構(gòu)建新的線程這一步。
方法是org.springframework.boot.loader.Launcher#launch(java.lang.String[], java.lang.String, java.lang.ClassLoader)反射調(diào)用邏輯比較簡單,這里就不再分析,比較關(guān)鍵的一點(diǎn)是,在調(diào)用main方法之前,將當(dāng)前線程的上下文類加載器設(shè)置成LaunchedURLClassLoader
protected?void?launch(String[]?args,?String?mainClass,?ClassLoader?classLoader)throws?Exception?{???Thread.currentThread().setContextClassLoader(classLoader);
???createMainMethodRunner(mainClass,?args,?classLoader).run();
}
Demo
public?static?void?main(String[]?args)?throws?ClassNotFoundException,?MalformedURLException?{????????JarFile.registerUrlProtocolHandler();
//?構(gòu)造LaunchedURLClassLoader類加載器,這里使用了2個(gè)URL,分別對(duì)應(yīng)jar包中依賴包spring-boot-loader和spring-boot,使用?"!/"?分開,需要org.springframework.boot.loader.jar.Handler處理器處理
????????LaunchedURLClassLoader?classLoader?=?new?LaunchedURLClassLoader(
????????????????new?URL[]?{
????????????????????????new?URL("jar:file:/E:/IdeaProjects/oneday-auth/oneday-auth-server/target/oneday-auth-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-loader-1.2.3.RELEASE.jar!/")
????????????????????????,?new?URL("jar:file:/E:/IdeaProjects/oneday-auth/oneday-auth-server/target/oneday-auth-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-2.1.3.RELEASE.jar!/")
????????????????},
????????????????Application.class.getClassLoader());
//?加載類
//?這2個(gè)類都會(huì)在第二步本地查找中被找出(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");
//????????SpringApplication.run(Application.class,?args);
????}
???????
????????<dependency>
????????????<groupId>org.springframework.bootgroupId>
????????????<artifactId>spring-boot-loaderartifactId>
????????????<version>2.1.3.RELEASEversion>
????????dependency>
????????<dependency>
????????????<groupId>org.springframework.bootgroupId>
????????????<artifactId>spring-boot-maven-pluginartifactId>
????????????<version>2.1.3.RELEASEversion>
????????dependency>
總結(jié)
對(duì)于源碼分析,這次的較大收獲則是不能一下子去追求弄懂源碼中的每一步代碼的邏輯,即便我知道該方法的作用。我們需要搞懂的是關(guān)鍵代碼,以及涉及到的知識(shí)點(diǎn)。
我從Maven的自定義插件開始進(jìn)行追蹤,鞏固了對(duì)Maven的知識(shí)點(diǎn),在這個(gè)過程中甚至了解到JDK對(duì)jar的讀取是有提供對(duì)應(yīng)的工具類。最后最重要的知識(shí)點(diǎn)則是自定義類加載器。整個(gè)代碼下來并不是說代碼究竟有多優(yōu)秀,而是要學(xué)習(xí)他因何而優(yōu)秀。
熱門內(nèi)容:
- Apache架構(gòu)師的30條設(shè)計(jì)原則!
- 盤點(diǎn)阿里巴巴 15 款開發(fā)者工具
- 詳細(xì)介紹!Linux 上幾種常用的文件傳輸方式
- ?看我如何作死 | 將CPU、IO打爆
- 面試時(shí)寫不出排序算法?看這篇就夠了
- 網(wǎng)易云音樂的消息隊(duì)列改造之路
- Elasticsearch性能優(yōu)化實(shí)戰(zhàn)指南
- 幾種常用 JSON 庫性能比較
喜歡就點(diǎn)個(gè)"在看"唄^_^
總結(jié)
以上是生活随笔為你收集整理的boot spring 怎么执行hql_彻底透析SpringBoot jar可执行原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: shell 死循环if判断_Shell的
- 下一篇: 散射理论方程_非弹性中子磁散射方法简介之