JAR 里到底有什么?manifest 怎么写、签名怎么保护、多版本 JAR 怎么按 JDK 版本选类?这篇真实打包、签名、篡改和在三个 JDK 上运行,逐项验证(多版本 JAR 在工程化部分有对应的篇目)。
没有 Main-Class:no main manifest attribute, in app_nomain.jar
jar 里的文件: ['META-INF/', 'META-INF/MANIFEST.MF', 'App.class']
META-INF/MANIFEST.MF 的内容:
Manifest-Version: 1.0
Created-By: 21.0.12.1 (Homebrew)
Main-Class: App
java -jar app.jar: Exception in thread "main" java.lang.NoClassDefFoundError: Util 同目录有 util.jar:App 运行,依赖 Util:hello from Util
把 app_cp.jar 挪到子目录(util.jar 不在它旁边):Exception in thread "main" java.lang.NoClassDefFoundError: Util
Class-Path 写得很长时,jar 工具把 manifest 里的行折成了 6 行,每行最多 72 字节(续行以一个空格开头):
|Manifest-Version: 1.0
|Main-Class: App
|Class-Path: dependency-00.jar dependency-01.jar dependency-02.jar depend
| ency-03.jar dependency-04.jar dependency-05.jar dependency-06.jar depen
| dency-07.jar jarsigner 输出: jar signed. | Warning: | The signer's certificate is self-signed.
签名后 jar 里的文件: ['META-INF/MANIFEST.MF', 'META-INF/DEV.SF', 'META-INF/DEV.EC', 'META-INF/', 'App.class']
jarsigner -verify: jar verified.
META-INF/DEV.SF 的开头:
Signature-Version: 1.0
Created-By: 21.0.12.1 (Homebrew)
SHA-384-Digest-Manifest: A7fVQmfYgpWmatD61P+7Zkg87YQ+ploPHOenIgw0Agsxg4I
jNNnDeBbW1PfCVQUZ
替换了 App.class 之后 jarsigner -verify: jarsigner: java.lang.SecurityException: SHA-384 digest error for App.class
java -jar tampered.jar: Exception in thread "main" java.lang.SecurityException: SHA-384 digest error for App.classjar 创建: 成功 jar 里的文件: ['META-INF/', 'META-INF/MANIFEST.MF', 'Main.class', 'Ver.class', 'META-INF/versions/17/', 'META-INF/versions/17/Ver.class', 'META-INF/versions/21/', 'META-INF/versions/21/Ver.class'] manifest 里有 Multi-Release: True 17:Ver.name() = versions/17 的实现 21:Ver.name() = versions/21 的实现 27:Ver.name() = versions/21 的实现 JDK 21,但 -Djdk.util.jar.enableMultiRelease=false:21:Ver.name() = base(Java 8 风格的基线实现)