Jar中的Manifest文件
写了个简单的Java代码,要怎么把它打包为jar包供jre去运行?
项目结构如下:
fuming@fumingdeiMac-Pro test % tree
.
├── src
│ └── main
│ └── Main.java
└── test.iml
2 directories, 2 files
其中,src/Main.java
的内容如下:
package main;
public class Main {
public static void main(String[] args) {
System.out.println(1);
}
}
先不放Manifest,直接打包,会怎样?
打包
无Manifest打包
直接编译java文件为class文件并打包:
fuming@MacBook-Pro src javac main/Main.java
fuming@MacBook-Pro src jar cf MyJar.jar main/*.class
fuming@MacBook-Pro src tree
.
├── MyJar.jar
└── main
├── Main.class
└── Main.java
1 directory, 3 files
fuming@MacBook-Pro src java -jar MyJar.jar
MyJar.jar中没有主清单属性
可以看到能打包成功,但是无法运行,因为MyJar.jar
中未指定程序入口是哪个函数,也就是“没有主清单属性”。
给无Manifest的jar包添加程序入口
下面命令更新了jar包,为其加入了程序入口:
fuming@MacBook-Pro src jar ufe MyJar.jar main.Main # 这里表示main.Main这个class,符号'.'可以用'/'替换
更新之后,解压MyJar.jar
,可以看到:
fuming@MacBook-Pro MyJar tree
.
├── META-INF
│ └── MANIFEST.MF
└── main
└── Main.class
2 directories, 2 files
fuming@MacBook-Pro MyJar cat META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 18.0.2.1 (Oracle Corporation)
Main-Class: main.Main
多了META-INF/MANIFEST.MF
,且其中的内容是jar
工具自己写入的。
有Manifest打包
在src/
下放置文件META-INF/MANIFEST.MF
,内容如下(注意文件后一定要有一个空行,不要使用Windows风格的换行符
)。
Manifest-Version: 1.0
Created-By: 18.0.2.1 (Oracle Corporation)
Main-Class: main.Main
然后即可开始打包。
先将java文件编译为class文件:
fuming@MacBook-Pro src javac main/Main.java
fuming@MacBook-Pro src tree
.
├── META-INF
│ └── MANIFEST.MF
└── main
├── Main.class
└── Main.java
2 directories, 3 files
fuming@MacBook-Pro src jar cvfm MyJar.jar META-INF/MANIFEST.MF main/*.class
已添加清单
正在添加: main/Main.class(输入 = 383) (输出 = 273)(压缩了 28%)
fuming@MacBook-Pro src java -jar MyJar.jar
1
运行没有问题,解压后其内容和上文给无Manifest的jar包添加程序入口
部分解压结果相同。
Manifest的使用
ClassPath
刚才打包的是test
项目,如果test
想调用其他人提供的jar包中的方法,应该配置ClassPath。
现在我们新建一个test2
项目,其中只有一个主类如下:
package org.flxdu.example;
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
}
public static int add(int a, int b) {
return a + b;
}
}
按照上文的方法进行打包得到Test2.jar
,确保jar包运行可以输出Hello world!
。
首先说一下在IntelliJ IDEA中如何使代码可以调用Test2.jar
中的org.flxdu.example.Main.add(int a, int b)
方法。
在IntelliJ IDEA中添加ClassPath
进入下图“Project Structure”位置:
在“SDKs”部分找到项目使用的JDK,新增ClassPath:
然后便可以import Test2.jar
中的类和静态方法了。
如下图:
在Manifest中使用ClassPath
上文我们在test
项目中调用了Test2.jar
。接下来我们打包test
项目。
首先在Manifest中加入ClassPath:
Manifest-Version: 1.0
Created-By: 18.0.2.1 (Oracle Corporation)
Main-Class: main.Main
Class-Path: Test2.jar # 这个相对路径是打为jar包后,和jar包同级的目录;可以写成绝对路径。
重新编译main/Main.java
:
fuming@MacBook-Pro src javac main/Main.java
main/Main.java:6: 错误: 程序包org.flxdu.example不存在
System.out.println(org.flxdu.example.Main.add(11, 2));
^
1 个错误
这里会报错,是因为我们上面的配置告诉了IDEA去哪里寻找org.flxdu.example
,但是终端中的javac
并没有被配置。
我们需要使用--class-path <path>, -classpath <path>, -cp <path>
来指定Test2.jar
的路径。
fuming@MacBook-Pro src ls
META-INF Test2.jar main
fuming@MacBook-Pro src javac -cp Test2.jar main/Main.java
fuming@MacBook-Pro src jar cvfm MyJar.jar META-INF/MANIFEST.MF main/*.class
已添加清单
正在添加: main/Main.class(输入 = 440) (输出 = 315)(压缩了 28%)
fuming@MacBook-Pro src java -jar MyJar.jar
13
可以看到没有问题。
如果把Test2.jar
移走呢:
fuming@MacBook-Pro src ls
META-INF MyJar.jar Test2.jar main
fuming@MacBook-Pro src mv Test2.jar main
fuming@MacBook-Pro src java -jar MyJar.jar
Exception in thread "main" java.lang.NoClassDefFoundError: org/flxdu/example/Main
at main.Main.main(Main.java:6)
Caused by: java.lang.ClassNotFoundException: org.flxdu.example.Main
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
... 1 more
可以看到MyJar.jar
找不到Test2.jar
,也就找不到org.flxdu.example.Main
,然后报错。
Setting Package Version Information
回到test
项目,其中,src/Main.java
的内容如下:
package main;
public class Main {
public static void main(String[] args) {
System.out.println(1);
}
}
将其编译为class文件。
在Manifest.txt
中写入信息,这些信息不会影响项目打包运行,只是向jar包使用者展示信息。
可以写入的内容有很多,详见Java™ Product Versioning。
在META-INF/MANIFRST.MF
中写入一些基本的信息:
Main-Class: main.Main
Name: main
Specification-Title: Test
Specification-Version: 1.2
Specification-Vendor: Example Tech, Inc.
Implementation-Title: main
Implementation-Version: 0.0.1
Implementation-Vendor: Example Tech, Inc.
目录结构:
fuming@fumingdeiMac-Pro test % tree
.
├── src
│ ├── META-INF
│ │ └── MANIFEST.MF
│ └── main
│ ├── Main.class
│ └── Main.java
└── test.iml
3 directories, 4 files
打包:
fuming@fumingdeiMac-Pro src % jar cvfm MyJar.jar META-INF/MANIFEST.MF main/*.class
已添加清单
正在添加: main/Main.class(输入 = 383) (输出 = 273)(压缩了 28%)
运行jar包,可以看到运行输出没有问题。
打包完毕后,需要使用JDK提供的类java.lang.Package
来读取这些内容,具体使用见Java™ Product Versioning。
Sealing Packages within a JAR File
包密封是指在生成JAR文件的时候,可以选择对JAR文件中的一个包进行密封。对JAR文件中的一个包进行密封是指这个包中定义的所有类都必须出自同一个JAR文件,否则JVM会抛出SecurityException。
https://docs.oracle.com/javase/tutorial/deployment/jar/sealman.html
Enhancing Security with Manifest Attributes
https://docs.oracle.com/javase/tutorial/deployment/jar/secman.html
Spring Boot项目Jar包中的Manifest文件
解压一个Spring Boot项目的jar包,可以看到如下目录结构:
fuming@MacBook-Pro app-0.0.1-SNAPSHOT tree -L 2
.
├── BOOT-INF
│ ├── classes # 项目源码
│ ├── classpath.idx
│ ├── layers.idx
│ └── lib # 依赖库,都是jar包
├── META-INF # 重点!!!!!
│ ├── MANIFEST.MF
│ └── maven
└── org
└── springframework # spring框架的一些class文件
7 directories, 3 files
查看META-INF/MANIFEST.MF
,内容如下:
Manifest-Version: 1.0
Created-By: Maven Jar Plugin 3.2.0
Build-Jdk-Spec: 13
Implementation-Title: app
Implementation-Version: 0.0.1-SNAPSHOT
Main-Class: org.springframework.boot.loader.JarLauncher # JAR包的主类,也就是程序的入口类
Start-Class: com.example.app.Application # Spring Boot应用的主类,也就是Spring Boot应用的入口类
Spring-Boot-Version: 2.4.2
Spring-Boot-Classes: BOOT-INF/classes/ # Spring Boot应用的类文件存放的路径
Spring-Boot-Lib: BOOT-INF/lib/ # Spring Boot应用的依赖库存放的路径
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx # Spring Boot应用的类路径索引文件
Spring-Boot-Layers-Index: BOOT-INF/layers.idx # Spring Boot应用的分层索引文件
运行jar包时,会先启动Spring Boot的JarLauncher
,然后启动项目的主类。
启动流程和细节可以查阅Spring Boot文档。