在 JAVA 語言這個圈子裡面摸爬滾打,除了對於語言層面和框架層面的學習之外,有一些東西它一直存在,但是確沒有對它們有足夠的重視,因為都覺得它是理所當然,比如 JAR 是個什麼?
提到 JAR,最先可能想到的就是依賴,比如 fastjson.jar ,它可以作為依賴在專案中來引用,但是不能通過 java -jar 來執行,這種就是非可執行的 JAR。另外一種,比如我們專案打包之後生成的 JAR (當然也可能是 war),我們可以通過 java -jar 來執行程式,我們把它稱之為可執行的 JAR。
JAR 作用大體可以分為以下幾種:
- 用於釋出和使用類庫
- 作為應用程式和擴充套件的構建單元
- 作為元件、applet 或者外掛程式的部署單位
- 用於打包與元件相關聯的輔助資源
基本概念
JAR 檔案是一種歸檔檔案,以 ZIP 格式構建,以 .jar 為副檔名。使用者可以使用 JDK 自帶的 jar 命令建立或提取 JAR 檔案。也可以使用其他 zip 壓縮工具,不過壓縮時 zip 檔案頭裡的條目順序很重要,因為 MANIFEST 檔案常需放在首位。JAR 檔案內的檔名是 Unicode 文字。
JAR 檔案(Java 歸檔,英語:Java Archive)是一種軟體包檔案格式,通常用於聚合大量的 Java 類檔案、相關的後設資料和資源(文字、圖片等)檔案到一個檔案,以便分發 Java 平臺應用軟體或庫。
以上來自維基百科
JAR 檔案格式提供了許多優勢和功能,其中很多是傳統的壓縮格式如 ZIP 或者 TAR 所沒有提供的。它們包括:
- 安全性:可以對 JAR 檔案內容加上數字化簽名。這樣,能夠識別簽名的工具就可以有選擇地為您授予軟體安全特權,這是其他檔案做不到的,它還可以檢測程式碼是否被篡改過。
- 減少下載時間:如果一個 applet 捆綁到一個 JAR 檔案中,那麼瀏覽器就可以在一個 HTTP 事務中下載這個 applet 的類檔案和相關的資源,而不是對每一個檔案開啟一個新連線。
- 壓縮:JAR 格式允許您壓縮檔案以提高儲存效率。
- 傳輸平臺擴充套件。Java 擴充套件框架 (Java Extensions Framework) 提供了向 Java 核心平臺新增功能的方法,這些擴充套件是用 JAR 檔案打包的 (Java 3D 和 JavaMail 就是由 Sun 開發的擴充套件例子 )。
- 包密封:儲存在 JAR 檔案中的包可以選擇進行 密封,以增強版本一致性和安全性。密封一個包意味著包中的所有類都必須在同一 JAR 檔案中找到。
- 包版本控制:一個 JAR 檔案可以包含有關它所包含的檔案的資料,如廠商和版本資訊。
- 可移植性:處理 JAR 檔案的機制是 Java 平臺核心 API 的標準部分。
JAR 檔案格式
這裡分別給出兩個 JAR 的解壓之後的示例
普通的 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 |