聊一聊 JAR 文件和 MANIFEST.MF

在 JAVA 語言這個圈子裏面摸爬滾打,除了對於語言層面和框架層面的學習以外,有一些東西它一直存在,可是確沒有對它們有足夠的重視,由於都以爲它是理所固然,好比 JAR 是個什麼?html

提到 JAR,最早可能想到的就是依賴,好比 fastjson.jar ,它能夠做爲依賴在項目中來引用,可是不能經過 java -jar 來執行,這種就是非可執行的 JAR。另一種,好比咱們項目打包以後生成的 JAR (固然也多是 war),咱們能夠經過 java -jar 來運行程序,咱們把它稱之爲可執行的 JAR。java

JAR 做用大致能夠分爲如下幾種:git

  • 用於發佈和使用類庫
  • 做爲應用程序和擴展的構建單元
  • 做爲組件、applet 或者插件程序的部署單位
  • 用於打包與組件相關聯的輔助資源

基本概念

JAR 文件是一種歸檔文件,以 ZIP 格式構建,以 .jar 爲文件擴展名。用戶可使用 JDK 自帶的 jar 命令建立或提取 JAR 文件。也可使用其餘 zip 壓縮工具,不過壓縮時 zip 文件頭裏的條目順序很重要,由於 MANIFEST 文件常需放在首位。JAR 文件內的文件名是 Unicode 文本。github

JAR 文件(Java 歸檔,英語:Java Archive)是一種軟件包文件格式,一般用於聚合大量的 Java 類文件、相關的元數據和資源(文本、圖片等)文件到一個文件,以便分發 Java 平臺應用軟件或庫。spring

以上來自維基百科express

JAR 文件格式提供了許多優點和功能,其中不少是傳統的壓縮格式如 ZIP 或者 TAR 所沒有提供的。它們包括:json

  • 安全性:能夠對 JAR 文件內容加上數字化簽名。這樣,可以識別簽名的工具就能夠有選擇地爲您授予軟件安全特權,這是其餘文件作不到的,它還能夠檢測代碼是否被篡改過。
  • 減小下載時間:若是一個 applet 捆綁到一個 JAR 文件中,那麼瀏覽器就能夠在一個 HTTP 事務中下載這個 applet 的類文件和相關的資源,而不是對每個文件打開一個新鏈接。
  • 壓縮:JAR 格式容許您壓縮文件以提升存儲效率。
  • 傳輸平臺擴展。Java 擴展框架 (Java Extensions Framework) 提供了向 Java 核心平臺添加功能的方法,這些擴展是用 JAR 文件打包的 (Java 3D 和 JavaMail 就是由 Sun 開發的擴展例子 )。
  • 包密封:存儲在 JAR 文件中的包能夠選擇進行 密封,以加強版本一致性和安全性。密封一個包意味着包中的全部類都必須在同一 JAR 文件中找到。
  • 包版本控制:一個 JAR 文件能夠包含有關它所包含的文件的數據,如廠商和版本信息。
  • 可移植性:處理 JAR 文件的機制是 Java 平臺核心 API 的標準部分。

JAR 文件格式

這裏分別給出兩個 JAR 的解壓以後的示例api

普通的 JAR 解壓以後的文件目錄

以 fastjson 爲例:瀏覽器

.
├── META-INF
│   ├── LICENSE.txt
│   ├── MANIFEST.MF
│   ├── NOTICE.txt
│   ├── maven
│   │   └── com.alibaba
│   │       └── fastjson
│   │           ├── pom.properties
│   │           └── pom.xml
│   └── services
│       ├── javax.ws.rs.ext.MessageBodyReader
│       ├── javax.ws.rs.ext.MessageBodyWriter
│       ├── javax.ws.rs.ext.Providers
│       └── org.glassfish.jersey.internal.spi.AutoDiscoverable
└── com
    └── alibaba
        └── fastjson
            ├── JSON.class
            ├── JSONArray.class
            ├── JSONAware.class
            ├── JSONException.class
            ├── JSONObject.class
            ....省略
複製代碼

可執行的 jar (以 SpringBoot 的 FAT JAR 爲例)

這個 jar 是從 start.spring.io 上下載下來的一個最簡單的 demo 打包來的安全

├── BOOT-INF
│   ├── classes
│   │   ├── application.properties
│   │   └── com
│   │       └── example   # 應用的.class 文件目錄
│   │           └── demo
│   │               └── DemoApplication.class
│   └── lib # 這裏存放的是應用的 Maven 依賴的jar包文件
│       ├── javax.annotation-api-1.3.2.jar
│       ├── jul-to-slf4j-1.7.26.jar
│       ├── log4j-api-2.11.2.jar
│       ├── log4j-to-slf4j-2.11.2.jar
│       ├── logback-classic-1.2.3.jar
│       ├── logback-core-1.2.3.jar
│       ├── slf4j-api-1.7.26.jar
│       ├── snakeyaml-1.23.jar
│       ├── spring-aop-5.1.8.RELEASE.jar
│       ├── spring-beans-5.1.8.RELEASE.jar
│       ├── spring-boot-2.1.6.RELEASE.jar
│       ├── spring-boot-autoconfigure-2.1.6.RELEASE.jar
│       ├── spring-boot-starter-2.1.6.RELEASE.jar
│       ├── spring-boot-starter-logging-2.1.6.RELEASE.jar
│       ├── spring-context-5.1.8.RELEASE.jar
│       ├── spring-core-5.1.8.RELEASE.jar
│       ├── spring-expression-5.1.8.RELEASE.jar
│       └── spring-jcl-5.1.8.RELEASE.jar
├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│       └── com.example
│           └── demo
│               ├── pom.properties
│               └── pom.xml
└── org
    └── springframework
        └── boot
            └── loader #存放的是 Spring boot loader 的 class 文件
                ├── 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
                │   ├── Archive$Entry.class
                │   ├── ...
                ├── data
                │   ├── RandomAccessData.class
                │   ├── ...
                ├── jar
                │   ├── AsciiBytes.class
                │   ├── ...
                └── util
                    └── SystemPropertyUtils.class
複製代碼

META-INF

大多數 JAR 文件包含一個 META-INF 目錄,它用於存儲包和擴展的配置數據,如安全性和版本信息。Java 2 平臺(標準版【J2SE】)識別並解釋 META-INF 目錄中的下述文件和目錄,以便配置應用程序、擴展和類裝載器:

  • MANIFEST.MF:這個 manifest 文件定義了與擴展和包相關的數據。
  • 經過 MAVEN 插件打包進來的文件好比:
    • maven
    • services : 存儲全部服務提供程序配置文件
  • 其餘的還有一些不常看到的:
    • INDEX.LIST :這個文件由 jar工具的新選項 -i生成,它包含在應用程序或者擴展中定義的包的位置信息。它是 JarIndex 實現的一部分,並由類裝載器用於加速類裝載過程。
    • .SF:這是 JAR 文件的簽名文件
    • .DSA:與簽名文件相關聯的簽名程序塊文件,它存儲了用於簽名 JAR 文件的公共簽名。
    • LICENSE.txt :證書信息
    • NOTICE.txt : 公告信息

可執行的 JAR

能夠執行的 JAR 與 普通的 JAR 最直接的區別就是可否經過 java -jar 來執行。

一個 可執行的 jar文件是一個自包含的 Java 應用程序,它存儲在特別配置的 JAR 文件中,能夠由 JVM 直接執行它而無需事先提取文件或者設置類路徑。要運行存儲在非可執行的 JAR 中的應用程序,必須將它加入到您的類路徑中,並用名字調用應用程序的主類。可是使用可執行的 JAR 文件,咱們能夠不用提取它或者知道主要入口點就能夠運行一個應用程序。可執行 JAR 有助於方便發佈和執行 Java 應用程序

一個可執行的 JAR 必須經過 menifest 文件的頭引用它所須要的全部其餘從屬 JAR。若是使用了 -jar選項,那麼環境變量 CLASSPATH 和在命令行中指定的全部類路徑都被 JVM 所忽略。

MANIFEST.MF 文件

當咱們用 JAR 命令打完包後,會在根目錄下面建立 META-INF 目錄,該目錄下面會有一些對該 JAR 包信息的描述,其中確定會有一個 MANIFEST.MF 文件,該文件包含了該 JAR 包的版本、建立人和類搜索路徑等信息。

  • FASTJSON jar 中的 MANIFEST.MF 文件

    Manifest-Version: 1.0              # 用來定義manifest文件的版本
    Archiver-Version: Plexus Archiver  # 詳見 http://codehaus-plexus.github.io/plexus-archiver/
    Built-By: wenshao                  # 構建者
    Created-By: Apache Maven 3.5.0  # # 聲明該文件的生成者,通常該屬性是由 jar 命令行工具生成的
    Build-Jdk: 1.8.0_162               # 基於構建的 JDK 版本
    複製代碼
  • SpringBoot demo 的 MANIFEST.MF 文件

    Manifest-Version: 1.0
    Implementation-Title: demo                     # 定義了擴展實現的標題
    Implementation-Version: 0.0.1-SNAPSHOT         # 定義擴展實現的版本
    Start-Class: com.example.demo.DemoApplication  # 啓動類
    Spring-Boot-Classes: BOOT-INF/classes/         # 編譯以後的 class 文件目錄
    Spring-Boot-Lib: BOOT-INF/lib/                 # 當前工程依賴的 jar 包目錄
    Build-Jdk-Spec: 1.8                            # 指定的 JDK 版本
    Spring-Boot-Version: 2.1.6.RELEASE             # SpringBoot 版本
    Created-By: Maven Archiver 3.4.0             
    Main-Class: org.springframework.boot.loader.JarLauncher  # Main 函數
    複製代碼

在 Java 平臺中, MANIFEST 文件是 JAR 歸檔中所包含的特殊文件,MANIFEST 文件被用來定義擴展或文件打包相關數據。

MANIFEST 文件做爲一個元數據文件,它包含了不一樣部分中的 k-v 對數據。

若是一個 JAR 文件被看成可執行文件,則其中的 MANIFEST 文件須要指出該程序的主類文件,如上面案例中的 SpringBoot demo 的那個 jar 中的MANIFEST 文件所示

MANIFEST 做用

從 MANIFEST 文件中提供的信息大概能夠了解到其基本做用

  • JAR 包基本信息描述
  • Main-Class 指定程序的入口,這樣能夠直接用java -jar xxx.jar來運行程序
  • Class-Path 指定jar包的依賴關係,class loader會依據這個路徑來搜索class

獲取 MANIFEST.MF

JDK 中提供了能夠獲取 jar 包中 MANIFEST.MF 文件信息的工具,能夠經過 java.util.jar 這個類庫來獲取。

JarFile jar = new JarFile(new File("/Users/glmapper/Documents/test/demo/target/demo-0.0.1-SNAPSHOT.jar"));
Manifest manifest = jar.getManifest();
Attributes mainAttributes = manifest.getMainAttributes();
for(Map.Entry<Object, Object> attrEntry : mainAttributes.entrySet()){
    System.out.println("main\t"+attrEntry.getKey()+":"+attrEntry.getValue());
}
Map<String, Attributes> entries = manifest.getEntries();
for(Map.Entry<String, Attributes> entry : entries.entrySet()) {
    Attributes values = entry.getValue();
    for (Map.Entry<Object, Object> attrEntry : values.entrySet()) {
        System.out.println(attrEntry.getKey() + ":" + attrEntry.getValue());
    }
}
複製代碼

執行結果爲:

main	Implementation-Title:demo
main	Implementation-Version:0.0.1-SNAPSHOT
main	Start-Class:com.example.demo.DemoApplication
main	Spring-Boot-Classes:BOOT-INF/classes/
main	Spring-Boot-Lib:BOOT-INF/lib/
main	Build-Jdk-Spec:1.8
main	Spring-Boot-Version:2.1.6.RELEASE
main	Created-By:Maven Archiver 3.4.0
main	Manifest-Version:1.0
main	Main-Class:org.springframework.boot.loader.JarLauncher

複製代碼

Jar 文件和 Manifest 在 java 中的定義

下面爲 JarFile 的定義,從代碼就能夠看出,前面咱們所介紹的 Jar 是以 ZIP 格式構建一種歸檔文件,由於它是 ZipFile 的子類。

public class JarFile extends ZipFile {
    private SoftReference<Manifest> manRef;
    private JarEntry manEntry;
    private JarVerifier jv;
    private boolean jvInitialized;
    private boolean verify;
    //指示是否存在Class-Path屬性(僅當hasCheckedSpecialAttributes爲true時纔有效)
    private boolean hasClassPathAttribute;
    // 若是清單檢查特殊屬性,則爲 true
    private volatile boolean hasCheckedSpecialAttributes;
    // 在SharedSecrets中設置JavaUtilJarAccess
    static {
        SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
    }
    /** * The JAR manifest file name.(JAR清單文件名) */
    public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
    // 省略其餘
}
複製代碼

下面是 Manifest 類的定義,用來描述 JAR 的 清單文件。從其屬性中也很好的觀察到,其存儲的就是 K-V 鍵值對數據。

public class Manifest implements Cloneable {
    // manifest main attributes
    private Attributes attr = new Attributes();
    // manifest entries
    private Map<String, Attributes> entries = new HashMap<>();
    // 省略其餘
}
複製代碼

小結

JAR 格式遠遠超出了一種壓縮格式,它有許多能夠改進效率、安全性和組織 Java 應用程序的功能。由於這些功能已經創建在覈心平臺 -- 包括編譯器和類裝載器 -- 中了,因此開發人員能夠利用 JAR 文件格式的能力簡化和改進開發和部署過程。

附:常見的 jar工具用法

功能 命令
用一個單獨的文件建立一個 JAR 文件 jar cf jar-file input-file...
用一個目錄建立一個 JAR 文件 jar cf jar-file dir-name
建立一個未壓縮的 JAR 文件 jar cf0 jar-file dir-name
更新一個 JAR 文件 jar uf jar-file input-file...
查看一個 JAR 文件的內容 jar tf jar-file
提取一個 JAR 文件的內容 jar xf jar-file
從一個 JAR 文件中提取特定的文件 jar xf jar-file archived-file...
運行一個打包爲可執行 JAR 文件的應用程序 java -jar app.jar

參考

相關文章
相關標籤/搜索