jar打包 剔除第三方依賴以及它的依賴_面試官:為什么Spring Boot的jar可以直接運行?...

來源:Gormat's Notes

fangjian0423.github.io/2017/05/31/springboot-executable-jar/

  • Spring Boot Loader抽象的一些類
  • JarLauncher的執行過程
  • 關于自定義的類加載器LaunchedURLClassLoader
  • Spring Boot Loader的作用

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

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

打包完生成的executable-jar-1.0-SNAPSHOT.jar內部的結構如下:

├── 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

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

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

打包出來fat jar內部有4種文件類型:

  1. META-INF文件夾:程序入口,其中MANIFEST.MF用于描述jar包的信息
  2. lib目錄:放置第三方依賴的jar包,比如springboot的一些jar包
  3. spring boot loader相關的代碼
  4. 模塊自身的代碼

MANIFEST.MF文件的內容:

Manifest-Version: 1.0
Implementation-Title: executable-jar
Implementation-Version: 1.0-SNAPSHOT
Archiver-Version: Plexus Archiver
Built-By: Format
Start-Class: spring.study.executablejar.ExecutableJarApplication
Implementation-Vendor-Id: spring.study
Spring-Boot-Version: 1.3.5.RELEASE
Created-By: Apache Maven 3.2.3
Build-Jdk: 1.8.0_20
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher

我們看到,它的Main-Class是org.springframework.boot.loader.JarLauncher,當我們使用java -jar執行jar包的時候會調用JarLauncher的main方法,而不是我們編寫的SpringApplication。

那么JarLauncher這個類是的作用是什么的?

它是SpringBoot內部提供的工具Spring Boot Loader提供的一個用于執行Application類的工具類(fat jar內部有spring loader相關的代碼就是因為這里用到了)。相當于Spring Boot Loader提供了一套標準用于執行SpringBoot打包出來的jar

Spring Boot Loader抽象的一些類

抽象類Launcher:各種Launcher的基礎抽象類,用于啟動應用程序;跟Archive配合使用;目前有3種實現,分別是JarLauncher、WarLauncher以及PropertiesLauncher

Archive:歸檔文件的基礎抽象類。JarFileArchive就是jar包文件的抽象。它提供了一些方法比如getUrl會返回這個Archive對應的URL;getManifest方法會獲得Manifest數據等。ExplodedArchive是文件目錄的抽象

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的執行過程

JarLauncher的main方法:

public?static?void?main(String[]?args)?{
????//?構造JarLauncher,然后調用它的launch方法。參數是控制臺傳遞的
????new?JarLauncher().launch(args);
}

JarLauncher被構造的時候會調用父類ExecutableArchiveLauncher的構造方法。

ExecutableArchiveLauncher的構造方法內部會去構造Archive,這里構造了JarFileArchive。構造JarFileArchive的過程中還會構造很多東西,比如JarFile,Entry …

JarLauncher的launch方法:

protected?void?launch(String[]?args)?{
??try?{
????//?在系統屬性中設置注冊了自定義的URL處理器:org.springframework.boot.loader.jar.Handler。如果URL中沒有指定處理器,會去系統屬性中查詢
????JarFile.registerUrlProtocolHandler();
????//?getClassPathArchives方法在會去找lib目錄下對應的第三方依賴JarFileArchive,同時也會項目自身的JarFileArchive
????//?根據getClassPathArchives得到的JarFileArchive集合去創建類加載器ClassLoader。這里會構造一個LaunchedURLClassLoader類加載器,這個類加載器繼承URLClassLoader,并使用這些JarFileArchive集合的URL構造成URLClassPath
????//?LaunchedURLClassLoader類加載器的父類加載器是當前執行類JarLauncher的類加載器
????ClassLoader?classLoader?=?createClassLoader(getClassPathArchives());
????//?getMainClass方法會去項目自身的Archive中的Manifest中找出key為Start-Class的類
????//?調用重載方法launch
????launch(args,?getMainClass(),?classLoader);
??}
??catch?(Exception?ex)?{
????ex.printStackTrace();
????System.exit(1);
??}
}

//?Archive的getMainClass方法
//?這里會找出spring.study.executablejar.ExecutableJarApplication這個類
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?{
??????//?創建一個MainMethodRunner,并把args和Start-Class傳遞給它
?Runnable?runner?=?createMainMethodRunner(mainClass,?args,?classLoader);
??????//?構造新線程
?Thread?runnerThread?=?new?Thread(runner);
??????//?線程設置類加載器以及名字,然后啟動
?runnerThread.setContextClassLoader(classLoader);
?runnerThread.setName(Thread.currentThread().getName());
?runnerThread.start();
}

MainMethodRunner的run方法:

@Override
public?void?run()?{
??try?{
????//?根據Start-Class進行實例化
????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");
????}
????//?調用
????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方法調用之后,內部會構造Spring容器,啟動內置Servlet容器等過程。這些過程我們都已經分析過了。

關于自定義的類加載器LaunchedURLClassLoader

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

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);
}

加載規則:

  1. 如果根類加載器存在,調用它的加載方法。這里是根類加載是ExtClassLoader
  2. 調用LaunchedURLClassLoader自身的findClass方法,也就是URLClassLoader的findClass方法
  3. 調用父類的loadClass方法,也就是執行默認的類加載順序(從BootstrapClassLoader開始從下往下尋找)

LaunchedURLClassLoader自身的findClass方法:

protected Class> findClass(final String name)
throws ClassNotFoundException
{
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction>() {
public Class> run() throws ClassNotFoundException {
// 把類名解析成路徑并加上.class后綴
String path = name.replace('.', '/').concat(".class");
// 基于之前得到的第三方jar包依賴以及自己的jar包得到URL數組,進行遍歷找出對應類名的資源
// 比如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!/中被找出
// 那么找出的資源對應的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的一個測試:

// 注冊org.springframework.boot.loader.jar.Handler URL協議處理器
JarFile.registerUrlProtocolHandler();
// 構造LaunchedURLClassLoader類加載器,這里使用了2個URL,分別對應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個類都會在第二步本地查找中被找出(URLClassLoader的findClass方法)
classLoader.loadClass("org.springframework.boot.loader.JarLauncher");
classLoader.loadClass("org.springframework.boot.SpringApplication");
// 在第三步使用默認的加載順序在ApplicationClassLoader中被找出
classLoader.loadClass("org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration");

Spring Boot Loader的作用

SpringBoot在可執行jar包中定義了自己的一套規則,比如第三方依賴jar包在/lib目錄下,jar包的URL路徑使用自定義的規則并且這個規則需要使用org.springframework.boot.loader.jar.Handler處理器處理。它的Main-Class使用JarLauncher,如果是war包,使用WarLauncher執行。這些Launcher內部都會另起一個線程啟動自定義的SpringApplication類。

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

【面試題專欄】

面試官:你對Redis緩存了解嗎?面對這11道面試題你是否有很多問號?2020年Java多線程與并發系列22道高頻面試題解析

從阿里、騰訊的面試真題中總結了這11個Redis高頻面試題

2020年Java基礎高頻面試題匯總(1.4W字詳細解析)

全網最全Spring系列面試題129道(附答案解析)

2萬字Java并發編程面試題整理(含答案,建議收藏)

85道Java微服務面試題整理(助力2020面試)

【2020年大廠面試必備】JVM與性能調優知識點整理

2019年面試官最喜歡問的28道ZooKeeper面試題

2020面試還搞不懂MyBatis?看看這27道面試題!(含答案和思維導圖)

Spring Cloud+Spring Boot高頻面試題解析

2019年常見的Linux面試題及答案解析,哪些你還不會?

2019年常見Elasticsearch面試題答案解析

18道kafka高頻面試題哪些你還不會?(含答案和思維導圖)

2019年12道RabbitMQ高頻面試題你都會了嗎?(含答案解析)

2019年Dubbo你掌握的如何?快看看這30道高頻面試題!

c71d012c7652abbb4c5e2193eaa323be.png

275ec8edc4b97f5e5d9f11b162fe322e.png?你在看嗎?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/259830.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/259830.shtml
英文地址,請注明出處:http://en.pswp.cn/news/259830.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CQRS架構圖

2019獨角獸企業重金招聘Python工程師標準>>> 轉載于:https://my.oschina.net/darkness/blog/814243

SQLite中不支持的sql語法

今天很自然的在寫Sql語句的時候用了Top&#xff0c;一開始沒發現問題&#xff0c;因為我從數據庫讀出的值正好是0&#xff0c;而我習慣變量定義的時候也都賦值0&#xff0c;可是到我不要0的時候我就發現問題了。后來才知道&#xff0c;可愛的小sqlite竟然有不支持的sql語法。 看…

Analyzer普通用戶登錄不了[從網絡訪問此計算機]

問題&#xff1a; 最近客戶諾奇反映說Analyzer普通用戶登錄不了&#xff0c;但是發現管理員又可以登錄&#xff0c;幾經周折發現原來是系統的本地安全策略設置了不讓遠程使用本地賬戶密碼登錄系統導致。解決方案&#xff1a; 修改本地安全策略的“從遠程訪問此計算機”中的用戶…

金蝶系統服務器要求,金蝶服務器安裝及其相關要求.doc

K/3WISE創新管理平臺 V12.2標準部署環境說明目錄1. 多語言部署規則21.1 客戶端多語言部署規則21.2 中間層多語言部署規則31.3 數據庫多語言部署規則31.4 人力資源、管理門戶、CRM多語言部署規則41.5 Citrix遠程接入多語言部署規則42. 多語言部署架構圖52.1 簡體中間層52.2 繁體…

源碼 移植_FreeModbus移植總結

modbus是一項工業上經常用到的通訊協議&#xff0c;而freemodbus是一款開源的從機協議棧。關于它的移植網上已經有了很多的文章&#xff0c;但是大多都只是針對其中部分問題的表述。本文將會把自己在移植freemodbus過程中遇到的問題以及freemodbus的源碼分析盡量表述清楚。&…

expect腳本的簡單應用

expect是一個用來處理交互的命令。借助于expect我們可以把交互過程寫在一個腳本上&#xff0c;使之自動化完成。expect最核心的四個命令&#xff1a;send:用于向進程發送字符串 except:從進程接收字符串 spawn:打開一個新的進程 interact&#xff1a;保持交互的狀態首先一個簡單…

ajax中datatype是json,dataType:'json'vs data:$ .ajax中的JSON.stringify(obj)

我有這個數據結構&#xff1a;var formValues {TemporaryToken: a.userStatus.get("TemporaryToken"),MemorableWordPositionAndValues:[{Position: a.userStatus.get("MemorableWordPositions")[0],Value: this.$([name"login-memorable-character-…

sqlserver 查詢中使用Union或Union All

在 程序人生網站上 看到了 這篇文章 就收藏了 哈 http://www.ourcodelife.com/article-415-1.html 首先&#xff0c;在程序人生網站上&#xff0c;需要負責任的指出的是在SQL Server查詢中使用Union或Union All后Order by排序無效&#xff0c;我不確認是不是微軟的bug&#xf…

word標題大綱級別_快速按標題層級把Word轉Excel—附詳細操作步驟

如何快速把層級分明的word文檔轉換成橫向從屬結構的excel表格一、問題描述文檔如下圖所示。文檔一共三個層次&#xff0c;大綱級別分別是1、2、3級&#xff0c;左則是其文檔結構圖&#xff0c;可以看出文檔層級分明。最終要將文檔轉換成如下橫向從屬結構的表格。一個層次的內容…

生成GUID唯一值的方法匯總(dotnet/javascript/sqlserver)

一、在 .NET 中生成1、直接用.NET Framework 提供的 Guid() 函數&#xff0c;此種方法使用非常廣泛。GUID&#xff08;全局統一標識符&#xff09;是指在一臺機器上生成的數字&#xff0c;它保證對在同一時空中的任何兩臺計算機都不會生成重復的 GUID 值&#xff08;即保證所有…

Thread.CurrentPrincipal HttpContext.Current.User

據說要這樣寫才穩妥 // This principal will flow throughout the request.VoyagerPrincipal principal new VoyagerPrincipal(yada, yada, yada); // Attach the new principal object to the current HttpContext objectHttpContext.Current.User principal; // Make sure …

江森系統設置服務器日期,江森自控METASYS操作手冊

? Alarm? Trend顯示窗口中的按鈕編號 A B按鈕說明允許您編輯所示項目的屬性。選擇Save按鈕可保存修改。 顯示這個窗口中以前顯示過的內容。每個窗口最多可顯示5個歷史項目。顯示已保存的下一個窗口中的內容。鎖定選中的顯示窗口(防止被其他拖拽來的項目覆蓋)。您可調整被鎖定…

servlet中getWriter和getOutputStream的區別

getWriter();getOutputStream();區別&#xff1a;1、getWriter()用于向客戶機回送字符數據2、getOutputStream()返回的對象&#xff0c;可以回送字符數據&#xff0c;也可以回送字節數據&#xff08;二進制數據&#xff09;如何選擇&#xff1a;若果我們回送字符數據&#xff0…

execve系統調用_張凱捷—系統調用分析(3) (基于最新Linux5.0版本系統調用日志收集系統)...

在上一篇文章《系統調用分析(2)》中介紹和分析了32位和64位的快速系統調用指令——sysenter/sysexit和syscall/sysret&#xff0c;以及內核對快速系統調用部分的相關代碼&#xff0c;并追蹤了一個用戶態下的系統調用程序運行過程。本篇中將基于最新的Linux-5.0內核&#xff0c;…

批量下載小說網站上的小說(python爬蟲)

隨便說點什么 因為在學python&#xff0c;所有自然而然的就掉進了爬蟲這個坑里&#xff0c;好吧&#xff0c;主要是因為我覺得爬蟲比較酷&#xff0c;才入坑的。 想想看&#xff0c;你可以批量自動的采集互聯網上海量的資料數據&#xff0c;是多么令人激動啊&#xff01; 所以我…

Playground

題意 &#xff1a;求被兩點分割的凸包面積的較小值 題意已經給出順時針啦 就是求以某一個點 和其他所有相鄰點組成三角形的面積&#xff0c;然后sum存和求兩點的時候就求出那兩點的之間所有三角形的和再減掉0點和那兩點的面積一減就是其中一個三角形的面積。轉載于:https://…

華為歐拉系統服務器開接口,華為操作系統 euleros

華為操作系統 euleros 內容精選換一換Atlas 900 AI集群安裝上架、服務器基礎參數配置、安裝操作系統等操作&#xff0c;請根據集群配置參見對應的手冊&#xff1a;《Atlas 900 PoD 用戶指南 (型號9000, 直流)》《Atlas 900 PoD 用戶指南 (型號9000, 交流)》《Atlas 900 計算節點…

對勾函數_對勾函數?2020福建省中考壓軸題分析

訓練營機密視頻大公開歡迎轉發、分享傳播知識&#xff0c;傳播力量&#xff01;福建也是全省統一考選擇題&#xff1a;這題考的是二次函數的性質&#xff0c;先求出對稱軸就好了填空題&#xff1a;這題看似有反比例&#xff0c;確實會用到反比例函數的對稱性。但其實重點是考察…

這個textview有問題嗎 為什么一使用就崩潰

問題描述<TextViewandroid:id"id/textview1"android:layout_columnSpan"4"android:layout_gravity"fill"android:gravity"right"android:text"0" />public class MainActivity extends Activity {Button one;TextView…

通過Ajax解析和jQuery寫了一個小小的導航條

最近在用ajax和jquery做開發&#xff0c;所以閑來無事寫了些小導航條&#xff0c;通過ajax解析XML文件動態的創建WEB網站的導航條。 <link href"css/style.css" rel"stylesheet" type"text/css" /><script src"js/jquery-1.8.2.mi…