您好,登錄后才能下訂單哦!
這篇文章主要介紹“Springboot中FatJar和Jar是什么”,在日常操作中,相信很多人在Springboot中FatJar和Jar是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Springboot中FatJar和Jar是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
Spring Boot應用可以使用spring-boot-maven-plugin
快速打包,構建一個可執行jar。Spring Boot內嵌容器,通過java -jar
命令便可以直接啟動應用。
雖然是一個簡單的啟動命令,背后卻藏著很多知識。今天帶著大家探索FAT JAR啟動的背后原理。本文主要包含以下幾個部分:
JAR 是什么。首先需要了解jar是什么,才知道java -jar
做了什么事情。
FatJar 有什么不同。 Spring Boot提供的可執行jar與普通的jar有什么區別。
啟動時的類加載原理。 啟動過程中類加載器做了什么?Spring Boot又如何通過自定義類加載器解決內嵌包的加載問題。
啟動的整個流程。最后整合前面三部分的內容,解析源碼看如何完成啟動。
JAR文件(Java歸檔,英語: Java ARchive)是一種軟件包文件格式,通常用于將大量的Java類文件、相關的元數據和資源(文本、圖片等)文件聚合到一個文件,以便分發Java平臺應用軟件或庫。簡單點理解其實就是一個壓縮包,既然是壓縮包那么為了提取JAR文件的內容,可以使用任何標準的unzip解壓縮軟件提取內容。或者使用Java虛擬機自帶命令jar -xf foo.jar
來解壓相應的jar文件。
JAR 可以簡單分為兩類:
非可執行JAR。打包時,不用指定main-class
,也不可運行。普通jar包可以供其它項目進行依賴。
可執行JAR。打jar包時,指定了main-class
類,可以通過java -jar xxx.jar
命令,執行main-class
的main
方法,運行jar包。可運行jar包不可被其他項目進行依賴。
不管是非可行JAR還是可執行JAR解壓后都包含兩部分:META-INF
目錄(元數據)和package
目錄(編譯后的class)。這種普通的jar不包含第三方依賴包,只包含應用自身的配置文件、class 等。
. ├── META-INF │ ├── MANIFEST.MF #定義 └── org # 包路徑(存放編譯后的class) └── springframework
JAR包的配置文件是META-INF
文件夾下的MANIFEST.MF
文件。主要配置信息如下:
Manifest-Version: 用來定義manifest文件的版本,例如:Manifest-Version: 1.0
Created-By: 聲明該文件的生成者,一般該屬性是由jar命令行工具生成的,例如:Created-By: Apache Ant 1.5.1
Signature-Version: 定義jar文件的簽名版本
Class-Path: 應用程序或者類裝載器使用該值來構建內部的類搜索路徑,可執行jar包里需要設置這個。
上面是普通jar包的屬性,可運行jar包的.MF文件中,還會有mian-class
或start-class
等屬性。如果依賴了外部jar包,還會在MF文件中配置lib路徑等信息。更多信息參見:maven為MANIFEST.MF文件添加內容的方法
至于可運行jar包和普通jar包的目錄結構,沒有什么特別固定的模式,總之,無論是什么結構,在.MF文件中,配置好jar包的信息,即可正常使用jar包了。
普通的jar只包含當前 jar的信息,不含有第三方 jar。當內部依賴第三方jar時,直接運行則會報錯,這時候需要將第三方jar內嵌到可執行jar里。將一個jar及其依賴的三方jar全部打到一個包中,這個包即為 FatJar。
Spring Boot
為了解決內嵌jar問題,提供了一套FatJar解決方案,分別定義了jar目錄結構和 MANIFEST.MF
。在編譯生成可執行 jar 的基礎上,使用spring-boot-maven-plugin
按Spring Boot 的可執行包標準repackage
,得到可執行的Spring Boot jar。根據可執行jar類型,分為兩種:可執行Jar和可執行war。
因為在新建的空的 SpringBoot 工程中并沒有任何地方顯示的引入或者編寫相關的類。實際上,對于每個新建的 SpringBoot 工程,可以在其 pom.xml 文件中看到如下插件:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
這個是SpringBoot官方提供的用于打包FatJar的插件,org.springframework.boot.loader
下的類其實就是通過這個插件打進去的;
下面是此插件將 loader 相關類打入 FatJar 的一個執行流程:
org.springframework.boot.maven#execute-> org.springframework.boot.maven#repackage -> org.springframework.boot.loader.tools.Repackager#repackage-> org.springframework.boot.loader.tools.Repackager#writeLoaderClasses-> org.springframework.boot.loader.tools.JarWriter#writeLoaderClasses
最終的執行方法就是下面這個方法,通過注釋可以看出,該方法的作用就是將 spring-boot-loader 的classes 寫入到 FatJar 中。
/** * Write the required spring-boot-loader classes to the JAR. * @throws IOException if the classes cannot be written */ @Override public void writeLoaderClasses() throws IOException { writeLoaderClasses(NESTED_LOADER_JAR); }
Spring Boot項目被編譯以后,在targert
目錄下存在兩個jar文件:一個是xxx.jar
和xxx.jar.original
。
其中xxx.jar.original
是maven編譯后的原始jar文件,即標準的java jar。該文件僅包含應用本地資源。 如果單純使用這個jar,無法正常運行,因為缺少依賴的第三方資源。
因此spring-boot-maven-plugin
插件對這個xxx.jar.original
再做一層加工,引入第三方依賴的jar包等資源,將其 "repackage"
為xxx.jar
。可執行Jar的文件結構如下圖所示:
. ├── BOOT-INF │ ├── classes │ │ ├── application.properties # 用戶-配置文件 │ │ └── com │ │ └── glmapper │ │ └── bridge │ │ └── boot │ │ └── BootStrap.class # 用戶-啟動類 │ └── lib │ ├── jakarta.annotation-api-1.3.5.jar │ ├── jul-to-slf4j-1.7.28.jar │ ├── log4j-xxx.jar # 表示 log4j 相關的依賴簡寫 ├── META-INF │ ├── MANIFEST.MF │ └── maven │ └── com.glmapper.bridge.boot │ └── guides-for-jarlaunch │ ├── pom.properties │ └── pom.xml └── org └── springframework └── boot └── loader ├── ExecutableArchiveLauncher.class ├── JarLauncher.class ├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class ├── LaunchedURLClassLoader.class ├── Launcher.class ├── MainMethodRunner.class ├── PropertiesLauncher$1.class ├── PropertiesLauncher$ArchiveEntryFilter.class ├── PropertiesLauncher$PrefixMatchingArchiveFilter.class ├── PropertiesLauncher.class ├── WarLauncher.class ├── archive │ ├── # 省略 ├── data │ ├── # 省略 ├── jar │ ├── # 省略 └── util └── SystemPropertyUtils.class
META-INF: 存放元數據。MANIFEST.MF 是 jar 規范,Spring Boot 為了便于加載第三方 jar 對內容做了修改;
org: 存放Spring Boot 相關類,比如啟動時所需的 Launcher 等;
BOOT-INF/class: 存放應用編譯后的 class 文件;
BOOT-INF/lib: 存放應用依賴的 JAR 包。
Spring Boot的MANIFEST.MF
和普通jar有些不同:
Spring-Boot-Version: 2.1.3.RELEASE Main-Class: org.springframework.boot.loader.JarLauncher Start-Class: com.rock.springbootlearn.SpringbootLearnApplication Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Build-Jdk: 1.8.0_131
Main-Class: 是java -jar
啟動引導類,但這里不是項目中的類,而是Spring Boot內部的JarLauncher。
Start-Class: 這個才是正在要執行的應用內部主類
所以java -jar
啟動的時候,加載運行的是JarLauncher。Spring Boot內部如何通過JarLauncher 加載Start-Class 執行呢?為了更清楚加載流程,我們先介紹下java -jar
是如何完成類加載邏輯的。
這里簡單說下java -jar
啟動時是如何完成記載類加載的。Java 采用了雙親委派機制,Java語言系統自帶有三個類加載器:
Bootstrap CLassloder: 最頂層的加載類,主要加載核心類庫
Extention ClassLoader: 擴展的類加載器,加載目錄%JRE_HOME%/lib/ext
目錄下的jar包和class文件。 還可以加載-D java.ext.dirs選項指定的目錄。
AppClassLoader: 是應用加載器。
默認情況下通過java -classpath
,java -cp
,java -jar
使用的類加載器都是AppClassLoader。 普通可執行jar通過java -jar
啟動后,使用AppClassLoader加載Main-class
類。 如果第三方jar不在AppClassLoader里,會導致啟動時候會報ClassNotFoundException。
例如在Spring Boot可執行jar的解壓目錄下,執行應用的主函數,就直接報該錯誤:
Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication
at com.glmapper.bridge.boot.BootStrap.main(BootStrap.java:13)
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 1 more
從異常堆棧來看,是因為找不到SpringApplication
這個類;這里其實還是比較好理解的,BootStrap
類中引入了SpringApplication
,但是這個類是在BOOT-INF/lib
下的,而java指令在啟動時未指明classpath
,依賴的第三方jar無法被加載。
Spring Boot JarLauncher啟動時,會將所有依賴的內嵌 jar (BOOT-INF/lib 目錄下) 和class(BOOT-INF/classes 目錄)都加入到自定義的類加載器LaunchedURLClassLoader中,并用這個ClassLoder去加載MANIFEST.MF配置Start-Class,則不會出現類找不到的錯誤。
LaunchedURLClassLoader是URLClassLoader的子類, URLClassLoader會通過URL[] 來搜索類所在的位置。Spring Boot 則將所需要的內嵌文檔組裝成URL[],最終構建LaunchedURLClassLoader類。
有了以上知識的鋪墊,我們看下整個 FatJar 啟動的過程會是怎樣。為了以便查看源碼和遠程調試,可以在 pom.xml 引入下面的配置:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-loader</artifactId> </dependency>
簡單概括起來可以分為幾步:
java -jar 啟動,AppClassLoader 則會加載 MANIFEST.MF 配置的Main-Class, JarLauncher。
JarLauncher啟動時,注冊URL關聯協議。
獲取所有內嵌的存檔(內嵌jar和class)
根據存檔的URL[]構建類加載器。
然后用這個類加載器加載Start-Class。 保證這些類都在同一個ClassLoader中。
到此,關于“Springboot中FatJar和Jar是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。