本文示例基于 Java 8 。
1、实战示例
1.1、入门示例
在当前目录下创建文件 HelloWorld.java ,内容如下:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("你好,lt");
}
}
此时是无法执行的:
$ java HelloWorld
错误: 找不到或无法加载主类 HelloWorld
需要先编译,然后去执行 class 文件:
$ javac HelloWorld.java
$ ls
HelloWorld.class HelloWorld.java
$ java HelloWorld
你好,lt
将class文件打包到 jar:
$ jar -cvf hello.jar HelloWorld.class
已添加清单
正在添加: HelloWorld.class(输入 = 425) (输出 = 303)(压缩了 28%)
此时当前目录会出现 hello.jar:
$ ls
HelloWorld.class HelloWorld.java hello.jar
查看 hello.jar 中文件:
$ jar -tf hello.jar
META-INF/
META-INF/MANIFEST.MF
HelloWorld.class
$ unzip -q -c hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.8.0_191
执行 hello.jar 中的 HelloWorld 类:
$ java -cp hello.jar HelloWorld
你好
由于没有在 jar 中指定主类,若直接用-jar
执行 hello.jar 会报错:
$ java -jar hello.jar
hello.jar中没有主清单属性
manifest.txt 内容(注意,最后一行后面一定要有回车换行):
Manifest-Version: 1.1
Created-By: lt
Main-Class: HelloWorld
更新 META-INF/MANIFEST.MF :
$ jar uvfm hello.jar ./manifest.txt
$ unzip -q -c hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.1
Created-By: lt
Main-Class: HelloWorld
换一种方式打包:
$ jar -cvfe hello2.jar HelloWorld HelloWorld.class
$ unzip -q -c hello2.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.8.0_191
Main-Class: HelloWorld
$ java -jar hello2.jar
你好,lt
再换一种方式打包:
$ jar -cvfm hello.jar manifest.txt HelloWorld.class
$ java -jar hello.jar
你好,lt
$ jar -cvmf manifest.txt hello.jar HelloWorld.class
$ java -jar hello.jar
你好,lt
1.2、示例:多个类的打包和执行
当前目录下文件结构:
$ tree
.
└── src
└── com
└── example
├── HelloWorld.java
└── Util.java
Util.java 内容:
package com.example;
public class Util {
public static int add(int a, int b) {
return a + b;
}
}
HelloWorld.java 内容如下:
package com.example;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("1+2=" + Util.add(1, 2));
}
}
打包方式1
编译 java 代码:
$ cd src
$ javac com/example/Util.java
$ javac com/example/HelloWorld.java
注意,如果是src作为路径,会报错:
$ javac src/com/example/Util.java
$ javac src/com/example/HelloWorld.java
src/com/example/HelloWorld.java:6: 错误: 找不到符号
System.out.println("1+2=" + Util.add(1, 2));
^
符号: 变量 Util
位置: 类 HelloWorld
1 个错误
如果只编译 com/example/HelloWorld.java
,com/example/Util.java
也会被自动编译。因为 HelloWorld 依赖 Util 。
查看编译结果:
$ tree
.
└── src
└── com
└── example
├── HelloWorld.class
├── HelloWorld.java
├── Util.class
└── Util.java
打jar包方式1:
$ cd src
$ jar cvf hello.jar com/example/Util.class com/example/HelloWorld.class
已添加清单
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%
$ cd ..
$ tree
.
└── src
├── com
│ └── example
│ ├── HelloWorld.class
│ ├── HelloWorld.java
│ ├── Util.class
│ └── Util.java
└── hello.jar
$ java -cp src/hello.jar com.example.HelloWorld
1+2=3
打jar包方式2:
$ cd src
$ jar cvf hello.jar **/*.class
已添加清单
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%)
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)
$ cd ..
$ java -cp src/hello.jar com.example.HelloWorld
1+2=3
打包方式2
将编译结果放在一个单独的目录:
$ tree .
.
├── output
└── src
└── com
└── example
├── HelloWorld.java
└── Util.java
$ javac -sourcepath src -d output **/*.java
$ tree .
.
├── output
│ └── com
│ └── example
│ ├── HelloWorld.class
│ └── Util.class
└── src
└── com
└── example
├── HelloWorld.java
└── Util.java
$ jar cvf hello.jar -C output .
已添加清单
正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%)
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)
$ jar tf hello.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/HelloWorld.class
com/example/Util.class
1.3、示例:多个类、清单文件、资源文件的打包和执行
目录结构:
$ tree .
.
├── manifest.txt
├── output
│ └── com
│ └── example
│ ├── HelloWorld.class
│ └── Util.class
├── resource
│ ├── META-INF
│ │ └── MANIFEST.MF
│ └── test.txt
└── src
└── com
└── example
├── HelloWorld.java
└── Util.java
$ cat manifest.txt
Manifest-Version: 1.1
Created-By: lt
Main-Class: HelloWorld
$ cat resource/META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.8-TEST
$ cat resource/test.txt
abc
方式1
$ jar cvf hello.jar -C output . -C resource .
已添加清单
正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%)
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)
正在忽略条目META-INF/
正在忽略条目META-INF/MANIFEST.MF
正在添加: test.txt(输入 = 3) (输出 = 5)(压缩了 -66%)
$ unzip -q -c hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.8.0_191
$ unzip -q -c hello.jar test.txt
abc
resource 目录中的 META-INF/MANIFEST.MF 没有被打包进去。
方式2
$ jar cvfm hello.jar ./resource/META-INF/MANIFEST.MF -C output . -C resource .
已添加清单
正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%)
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)
正在忽略条目META-INF/
正在忽略条目META-INF/MANIFEST.MF
正在添加: test.txt(输入 = 3) (输出 = 5)(压缩了 -66%)
$ unzip -q -c hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.8-TEST
通过 m 选项指定 ./resource/META-INF/MANIFEST.MF
,将 MANIFEST.MF 放了进去。
方式3
$ jar cvfm hello.jar ./manifest.txt -C output . -C resource .
已添加清单
正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%)
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)
正在忽略条目META-INF/
正在忽略条目META-INF/MANIFEST.MF
正在添加: test.txt(输入 = 3) (输出 = 5)(压缩了 -66%)
$ unzip -q -c hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.1
Created-By: lt
Main-Class: HelloWorld
将 manifest.txt 的内容复制到了 jar 包的 META-INF/MANIFEST.MF 中。
方式4
$ jar cvfM hello.jar ./manifest.txt -C output . -C resource .
正在添加: manifest.txt(输入 = 60) (输出 = 62)(压缩了 -3%)
正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%)
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)
正在添加: META-INF/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: META-INF/MANIFEST.MF(输入 = 43) (输出 = 42)(压缩了 2%)
正在添加: test.txt(输入 = 4) (输出 = 6)(压缩了 -50%)
参数M表示不自动生成 META-INF/MANIFEST.MF
。所以将resource 中的 META-INF/MANIFEST.MF 打包进来。
1.4、示例:依赖第三方库的打包和执行
以 guava 为例,在 https://github.com/google/guava/releases 下载 jar。
目录结构:
$ tree .
.
├── lib
│ └── guava-31.1-jre.jar
└── src
└── com
└── example
└── HelloGuava.java
错误的编译方式:
$ javac -sourcepath ./src -d ./output **/*.java
src/com/example/HelloGuava.java:4: 错误: 程序包com.google.common.collect不存在
import com.google.common.collect.Lists;
^
src/com/example/HelloGuava.java:8: 错误: 找不到符号
List<String> list = Lists.newArrayList("a", "b");
^
符号: 变量 Lists
位置: 类 HelloGuava
2 个错误
应该用 -cp 指定依赖的jar:
$ javac -cp lib/guava-31.1-jre.jar -sourcepath ./src -d ./output **/*.java
$ tree
.
├── lib
│ └── guava-31.1-jre.jar
├── output
│ └── com
│ └── example
│ └── HelloGuava.class
└── src
└── com
└── example
└── HelloGuava.java
打包:
$ jar cvf hello.jar -C output .
执行:
$ java -cp lib/guava-31.1-jre.jar:hello.jar com.example.HelloGuava
[a, b]
$ java -cp lib/guava-31.1-jre.jar:output com.example.HelloGuava
[a, b]
2、命令详解: jar 命令
jar 参数
$ jar
用法: jar {ctxui}[vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] files ...
选项:
-c 创建新档案
-t 列出档案目录
-x 从档案中提取指定的 (或所有) 文件
-u 更新现有档案
-v 在标准输出中生成详细输出
-f 指定档案文件名
-m 包含指定清单文件中的清单信息
-n 创建新档案后执行 Pack200 规范化
-e 为捆绑到可执行 jar 文件的独立应用程序
指定应用程序入口点
-0 仅存储; 不使用任何 ZIP 压缩
-P 保留文件名中的前导 '/' (绝对路径) 和 ".." (父目录) 组件
-M 不创建条目的清单文件
-i 为指定的 jar 文件生成索引信息
-C 更改为指定的目录并包含以下文件
如果任何文件为目录, 则对其进行递归处理。
清单文件名, 档案文件名和入口点名称的指定顺序
与 'm', 'f' 和 'e' 标记的指定顺序相同。
示例 1: 将两个类文件归档到一个名为 classes.jar 的档案中:
jar cvf classes.jar Foo.class Bar.class
示例 2: 使用现有的清单文件 'mymanifest' 并
将 foo/ 目录中的所有文件归档到 'classes.jar' 中:
jar cvfm classes.jar mymanifest -C foo/ .
参数 f 对应 jar 文件名,m 对应 manifest 文件名,e对应入口类的全路径名(例如Hello、com.example.Hello)。文件名顺序一定要和m、f、e参数顺序一致。 下面两个是等价的。
jar cvfm classes.jar mymanifest -C foo/ .
jar cvmf mymanifest classes.jar -C foo/ .
ctxui
这几个参数不能同时出现。
更新 manifest
$ jar uvfm <jar-name>.jar <manifest-file-path>
示例:
$ jar -cvf hello.jar HelloWorld.class
$ jar tf hello.jar
META-INF/
META-INF/MANIFEST.MF
HelloWorld.class
$ unzip -q -c hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.8.0_191
manifest.txt 文件内容:
Manifest-Version: 1.1
Created-By: lt
Main-Class: HelloWorld
manifest.txt 文件名换成其他名字也可以的。
更新操作:
$ jar uvfm hello.jar ./manifest.txt
... java.util.jar.Attributes read
WARNING: Duplicate name in Manifest: Manifest-Version.
Ensure that the manifest does not have duplicate entries, and
that blank lines separate individual sections in both your
manifest and in the META-INF/MANIFEST.MF entry in the jar file.
... java.util.jar.Attributes read
WARNING: Duplicate name in Manifest: Created-By.
Ensure that the manifest does not have duplicate entries, and
that blank lines separate individual sections in both your
manifest and in the META-INF/MANIFEST.MF entry in the jar file.
已更新清单
$ unzip -q -c hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.1
Created-By: lt
注意,Main-Class 没有被补充进去。这是因为 manifest.txt 最后一行没有回车。
查看jar中文件列表
$ jar tf <jar-name>.jar
示例:
$ jar tf hello.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/HelloWorld.class
com/example/Util.class
将指定目录中的 class 文件放入 jar 中
$ jar cvf <jar-name>.jar -C <dir-path> .
示例:
$ tree .
.
├── output
│ └── com
│ └── example
│ ├── HelloWorld.class
│ └── Util.class
└── src
└── com
└── example
├── HelloWorld.java
└── Util.java
将 output 中文件放入 jar 中:
$ jar cvf hello.jar -C output .
已添加清单
正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%)
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)
$ jar tf hello.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/HelloWorld.class
com/example/Util.class
将 manifest 和 class 一起打包
示例:
jar -cvfm hello.jar ./manifest.txt **/*.class
-C 参数
例如:
jar cvf hello.jar -C output .
表示临时切换到 output 目录,再执行不带 -C 参数的 jar 命令。output
后面的.
代表相对 output 的当前目录。
同时打包 class 、资源文件
示例:
jar cvf hello.jar X1.class X1.class X3.png
3、命令详解:javac 命令
编译指定目录下的所有 java 文件
比如目录是src,使用 javac -sourcepath src **/*.java
即可。 示例:
$ tree .
.
└── src
└── com
└── example
├── HelloWorld.java
└── Util.java
$ javac -sourcepath src **/*.java
$ tree .
.
└── src
└── com
└── example
├── HelloWorld.class
├── HelloWorld.java
├── Util.class
└── Util.java
一次编译多个文件
示例:
$ javac com/example/HelloWorld.java com/example/Util.java
编译结果放到指定目录
$ tree .
.
├── output
└── src
└── com
└── example
├── HelloWorld.java
└── Util.java
$ javac -sourcepath src -d output **/*.java
$ tree .
.
├── output
│ └── com
│ └── example
│ ├── HelloWorld.class
│ └── Util.class
└── src
└── com
└── example
├── HelloWorld.java
└── Util.java
4、命令详解释:java 命令
-cp 参数
-cp
全称-classpath
,代表 目录和 zip/jar 文件的类搜索路径。 多个路径使用分隔符连接,Windows使用分号;
,Linux下使用冒号:
。 使用通配符时,不能添加.jar扩展名。
示例:
$ tree
.
├── lib
│ └── guava-31.1-jre.jar
├── output
│ └── com
│ └── example
│ └── HelloGuava.class
└── src
└── com
└── example
└── HelloGuava.java
$ jar cvf hello.jar -C output .
$ java -cp lib/guava-31.1-jre.jar:hello.jar com.example.HelloGuava
[a, b]
$ java -cp "lib/*:hello.jar" com.example.HelloGuava
[a, b]
jar 与 class 混合
下面的示例中 HelloGuava 没有打包到 jar 中。
$ tree
.
├── lib
│ └── guava-31.1-jre.jar
├── output
│ └── com
│ └── example
│ └── HelloGuava.class
└── src
└── com
└── example
└── HelloGuava.java
$ java -cp lib/guava-31.1-jre.jar:output com.example.HelloGuava
[a, b]
5、其他
查看 jar 中某个文件的内容
例如查看 test.jar 中 META-INF/MANIFEST.MF
文件内容。
unzip -q -c test.jar META-INF/MANIFEST.MF
解压 jar 到指定目录
示例:
unzip hello.jar -d dest/