溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Springboot中FatJar和Jar是什么

發(fā)布時間:2023-02-03 09:29:25 來源:億速云 閱讀:118 作者:iii 欄目:開發(fā)技術

這篇文章主要介紹“Springboot中FatJar和Jar是什么”,在日常操作中,相信很多人在Springboot中FatJar和Jar是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Springboot中FatJar和Jar是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

    導讀

    Spring Boot應用可以使用spring-boot-maven-plugin快速打包,構建一個可執(zhí)行jar。Spring Boot內嵌容器,通過java -jar命令便可以直接啟動應用。

    雖然是一個簡單的啟動命令,背后卻藏著很多知識。今天帶著大家探索FAT JAR啟動的背后原理。本文主要包含以下幾個部分:

    • JAR 是什么。首先需要了解jar是什么,才知道java -jar做了什么事情。

    • FatJar 有什么不同。 Spring Boot提供的可執(zhí)行jar與普通的jar有什么區(qū)別。

    • 啟動時的類加載原理。 啟動過程中類加載器做了什么?Spring Boot又如何通過自定義類加載器解決內嵌包的加載問題。

    • 啟動的整個流程。最后整合前面三部分的內容,解析源碼看如何完成啟動。

    JAR 是什么

    JAR簡介

    JAR文件(Java歸檔,英語: Java ARchive)是一種軟件包文件格式,通常用于將大量的Java類文件、相關的元數據和資源(文本、圖片等)文件聚合到一個文件,以便分發(fā)Java平臺應用軟件或庫。簡單點理解其實就是一個壓縮包,既然是壓縮包那么為了提取JAR文件的內容,可以使用任何標準的unzip解壓縮軟件提取內容。或者使用Java虛擬機自帶命令jar -xf foo.jar來解壓相應的jar文件。

    JAR 可以簡單分為兩類:

    • 非可執(zhí)行JAR。打包時,不用指定main-class,也不可運行。普通jar包可以供其它項目進行依賴。

    • 可執(zhí)行JAR。打jar包時,指定了main-class類,可以通過java -jar xxx.jar命令,執(zhí)行main-classmain方法,運行jar包??蛇\行jar包不可被其他項目進行依賴。

    JAR結構

    包結構

    不管是非可行JAR還是可執(zhí)行JAR解壓后都包含兩部分:META-INF目錄(元數據)和package目錄(編譯后的class)。這種普通的jar不包含第三方依賴包,只包含應用自身的配置文件、class 等。

    .
    ├── META-INF
    │   ├── MANIFEST.MF  #定義
    └── org  # 包路徑(存放編譯后的class)
        └── springframework
    描述文件MANIFEST.MF

    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: 應用程序或者類裝載器使用該值來構建內部的類搜索路徑,可執(zhí)行jar包里需要設置這個。

    上面是普通jar包的屬性,可運行jar包的.MF文件中,還會有mian-classstart-class等屬性。如果依賴了外部jar包,還會在MF文件中配置lib路徑等信息。更多信息參見:maven為MANIFEST.MF文件添加內容的方法

    至于可運行jar包普通jar包的目錄結構,沒有什么特別固定的模式,總之,無論是什么結構,在.MF文件中,配置好jar包的信息,即可正常使用jar包了。

    FatJar有什么不同

    什么是FatJar?

    普通的jar只包含當前 jar的信息,不含有第三方 jar。當內部依賴第三方jar時,直接運行則會報錯,這時候需要將第三方jar內嵌到可執(zhí)行jar里。將一個jar及其依賴的三方jar全部打到一個包中,這個包即為 FatJar。

    SpringBoot FatJar解決方案

    Spring Boot為了解決內嵌jar問題,提供了一套FatJar解決方案,分別定義了jar目錄結構MANIFEST.MF。在編譯生成可執(zhí)行 jar 的基礎上,使用spring-boot-maven-plugin按Spring Boot 的可執(zhí)行包標準repackage,得到可執(zhí)行的Spring Boot jar。根據可執(zhí)行jar類型,分為兩種:可執(zhí)行Jar和可執(zhí)行war。

    spring-boot-maven-plugin打包過程

    因為在新建的空的 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 的一個執(zhí)行流程:

    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

    最終的執(zhí)行方法就是下面這個方法,通過注釋可以看出,該方法的作用就是將 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.jarxxx.jar.original

    • 其中xxx.jar.original是maven編譯后的原始jar文件,即標準的java jar。該文件僅包含應用本地資源。 如果單純使用這個jar,無法正常運行,因為缺少依賴的第三方資源。

    • 因此spring-boot-maven-plugin插件對這個xxx.jar.original再做一層加工,引入第三方依賴的jar包等資源,將其 "repackage"xxx.jar??蓤?zhí)行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 規(guī)范,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: 這個才是正在要執(zhí)行的應用內部主類

    所以java -jar啟動的時候,加載運行的是JarLauncher。Spring Boot內部如何通過JarLauncher 加載Start-Class 執(zhí)行呢?為了更清楚加載流程,我們先介紹下java -jar是如何完成類加載邏輯的。

    啟動時的類加載原理

    這里簡單說下java -jar啟動時是如何完成記載類加載的。Java 采用了雙親委派機制,Java語言系統(tǒng)自帶有三個類加載器:

    • Bootstrap CLassloder: 最頂層的加載類,主要加載核心類庫

    • Extention ClassLoader: 擴展的類加載器,加載目錄%JRE_HOME%/lib/ext目錄下的jar包和class文件。 還可以加載-D java.ext.dirs選項指定的目錄。

    • AppClassLoader: 是應用加載器。

    默認情況下通過java -classpath,java -cp,java -jar使用的類加載器都是AppClassLoader。 普通可執(zhí)行jar通過java -jar啟動后,使用AppClassLoader加載Main-class類。 如果第三方jar不在AppClassLoader里,會導致啟動時候會報ClassNotFoundException。

    例如在Spring Boot可執(zhí)行jar的解壓目錄下,執(zhí)行應用的主函數,就直接報該錯誤:

    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,則不會出現(xiàn)類找不到的錯誤。

    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關聯(lián)協(xié)議。

    • 獲取所有內嵌的存檔(內嵌jar和class)

    • 根據存檔的URL[]構建類加載器。

    • 然后用這個類加載器加載Start-Class。 保證這些類都在同一個ClassLoader中。

    到此,關于“Springboot中FatJar和Jar是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

    向AI問一下細節(jié)

    免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI