dustland

dustball in dustland

jar包是什么

Crack jar

jar包是啥

jar(java archieve file),java归档文件

与zip比较

其内容和zip文件非常相似,甚至可以直接使用360压缩这种软件解压

唯一的区别就是,jar中有一个META-INF目录,这里面有一个MANIFEST.MF文件,该文件可能长这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Manifest-Version: 1.0
Implementation-Title: challenge
Implementation-Version: 0.0.1-SNAPSHOT
Built-By: shiyu
Implementation-Vendor-Id: io.tricking
Spring-Boot-Version: 2.1.0.RELEASE
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: io.tricking.challenge.ChallengeApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.5.3
Build-Jdk: 1.8.0_102
Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo
ot-starter-parent/challenge

其中最关键的是Main-Class信息,即jar包的入口点

有些jar包可以通过java -jar a.jar这种命令被执行,此时Main-Class就指定从哪个类的Main函数开始执行

可以理解为,这个META-INF/MANIFEST.MF就是一个清单,表明该jar包的概要信息.

jar包其他文件主就是class字节码文件等

打包目的

打jar包的目的,实际上和c/c++中制作.lib静态库或者.dll动态库一样,就一个文件,方便别人拷贝使用

模块化,标准化

只要写好文档,告诉别人如何调用jar包中的类和函数即可

maven管理的第三方库,都是使用jar包的形式提供的

image-20230126155922111

jar vs war

war包是Sun公司提出的web应用程序格式

两者相同点,都是一个压缩包

不同点,目录结构有区别

凡是能打war包的东西,必然能够打jar包

如何打包

源代码

源代码结构

1
2
3
4
5
6
7
8
9
10
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# tree src -f
src
└── src/top
└── src/top/dustball
├── src/top/dustball/Dao
│   └── src/top/dustball/Dao/User.java
└── src/top/dustball/Main.java

3 directories, 2 files

其中User.java是一个pojo

1
2
3
4
5
6
7
8
9
10
11
12
13
package top.dustball.Dao;
public class User {
String username;
String password;

public User(String username,String password){
this.username=username;
this.password=password;
}
public String toString(){
return "["+username+","+password+"]";
}
}

Main.java是程序入口,依赖User类

1
2
3
4
5
6
7
8
package top.dustball;
import top.dustball.Dao.User;
public class Main {
public static void main(String[] args) {
User user = new User("vader", "sjh");
System.out.println(user);
}
}

javac编译

打入jar包的都是class字节码文件,没有java源文件,因此首先需要javac编译

可以使用javac <sourcefile> -d out将所有字节码文件打包到out目录下

这里sourcefile没有找到一个一劳永逸的方法

每个javac命令最多只能编译一个目录下的所有java文件

1
2
3
4
top.dustball/
Dao/
User.java
Main.java

这里Dao/User.java和Main.java在不同的目录下,并且Main.java中依赖User.java,因此需要写一个很长的编译命令

1
2
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# javac src/top/dustball/Dao/*.java src/top/dustball/*.java -d out

更好的方法是将所有需要编译的源代码文件写到一份清单中,然后让javac根据清单工作,比如在根目录下写一个JavaList.txt

1
2
src/top/dustball/Dao/User.java
src/top/dustball/Main.java

此时在此处执行javac,就是javac的当前工作目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# javac @JavaList.txt -d out

┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# ls
bin JavaList.txt lib out README.md src

┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# tree -f out
out
└── out/top
└── out/top/dustball
├── out/top/dustball/Dao
│   └── out/top/dustball/Dao/User.class
└── out/top/dustball/Main.class

3 directories, 2 files

此时在根目录下已经生成了out目录,保留了包结构和所有class文件,下一步就是打jar包了

jar打包

1
2
3
4
5
6
7
8
9
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# jar -cvf Main.jar out/top/dustball/Main.class out/top/dustball/Dao/User.class
added manifest
adding: out/top/dustball/Main.class(in = 523) (out= 341)(deflated 34%)
adding: out/top/dustball/Dao/User.class(in = 897) (out= 485)(deflated 45%)

┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# ls *.jar
Main.jar

此时在jar工作目录,也就是跟目录下,生成了Main.jar其中都有啥呢?

1
2
3
4
5
6
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# jar -tf Main.jar
META-INF/
META-INF/MANIFEST.MF
out/top/dustball/Main.class
out/top/dustball/Dao/User.class

两个class都已经打包进入了Main.jar

关键在这个META-INF/MANIFEST.MF

提取Main.jar之后,该文件长这样

1
2
Manifest-Version: 1.0
Created-By: 11.0.17 (Debian)

唯一的有效信息是jdk版本11.0.17,Debian操作系统

没有Main-Class入口点信息,显然直接java -jar是不可以执行的

1
2
3
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# java -jar Main.jar
no main manifest attribute, in Main.jar

没有主属性清单

怎么改呢?

写到这里发现一个重大错误,包名不统一了

啥意思呢?

之前javac,jar的工作目录都是根目录,而包在src路径之下,是根目录的子目录

1
2
3
4
5
6
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# jar -tf Main.jar
META-INF/
META-INF/MANIFEST.MF
out/top/dustball/Main.class
out/top/dustball/Dao/User.class

此处out路径被当作了包的最外层,

可能是jar打包认为out也是包路径,但是class自己认为out不是包路径,此后就出错了

而实际上应该让包从top开始,如下:

1
2
3
4
5
6
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# jar -tf Main.jar
META-INF/
META-INF/MANIFEST.MF
top/dustball/Main.class
top/dustball/Dao/User.class

按照这样修改之后,在META-INF/MANIFEST.MF中这样写:

1
2
3
4
Manifest-Version: 1.0
Created-By: 11.0.17 (Debian)
Main-Class: top.dustball.Main

这就指定好了入口类

然后重新打包,这一次需要-m指定META-INF/MANIFEST.MF文件,作为jar包清单

1
2
3
4
5
6
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire]
└─# jar -cvfm Main.jar META-INF/MANIFEST.MF top/dustball/Main.class top/dustball/Dao
added manifest
adding: top/dustball/Main.class(in = 523) (out= 341)(deflated 34%)
adding: top/dustball/Dao/(in = 0) (out= 0)(stored 0%)
adding: top/dustball/Dao/User.class(in = 897) (out= 485)(deflated 45%)

执行

1
2
3
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire]
└─# java -jar Main.jar
[vader,sjh]

更灵活的打包方式

javac编译时,使用清单列出需要编译的源文件是一个好办法

jar打包时有没有更方便的方法呢?

刚才我们有两个打jar包的方式,其一是直接指定所有class文件

1
jar -cvf Main.jar out/top/dustball/Main.class out/top/dustball/Dao/User.class

此时Manifest文件是自动生成的,不包含入口点等信息

需要编写Manifest文件之后重新打包

1
jar -cvfm Main.jar META-INF/MANIFEST.MF top/dustball/Main.class top/dustball/Dao

奇怪的是,当时Dao/User.class没有敲上,敲到Dao就停了,也成功打包了

现在把修改后的META-INF/MANIFEST.MF整体拷贝到out目录下,现在的out目录:

1
2
3
4
5
6
7
8
9
10
11
12
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree/out]
└─# tree -f
.
├── ./META-INF
│   └── ./META-INF/MANIFEST.MF
└── ./top
└── ./top/dustball
├── ./top/dustball/Dao
│   └── ./top/dustball/Dao/User.class
└── ./top/dustball/Main.class

4 directories, 3 files

在此处指定META-INF/MANIFEST.MF并打包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree/out]
└─# jar -cvfm Main.jar META-INF/MANIFEST.MF *
added manifest
ignoring entry META-INF/
ignoring entry META-INF/MANIFEST.MF
adding: top/(in = 0) (out= 0)(stored 0%)
adding: top/dustball/(in = 0) (out= 0)(stored 0%)
adding: top/dustball/Dao/(in = 0) (out= 0)(stored 0%)
adding: top/dustball/Dao/User.class(in = 897) (out= 485)(deflated 45%)
adding: top/dustball/Main.class(in = 523) (out= 341)(deflated 34%)

┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree/out]
└─# ls
Main.jar META-INF top

执行jar包

1
2
3
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree/out]
└─# java -jar Main.jar
[vader,sjh]

更更灵活的打包方式

在打包的时候不指定入口点而在执行的时候指定入口点

out目录状态:

1
2
3
4
5
6
7
8
9
10
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree/out]
└─# tree -f
.
└── ./top
└── ./top/dustball
├── ./top/dustball/Dao
│   └── ./top/dustball/Dao/User.class
└── ./top/dustball/Main.class

3 directories, 2 files

直接再次处打jar包

1
2
3
4
5
6
7
8
9
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree/out]
└─# jar -cvf Main.jar *
added manifest
adding: top/(in = 0) (out= 0)(stored 0%)
adding: top/dustball/(in = 0) (out= 0)(stored 0%)
adding: top/dustball/Dao/(in = 0) (out= 0)(stored 0%)
adding: top/dustball/Dao/User.class(in = 897) (out= 485)(deflated 45%)
adding: top/dustball/Main.class(in = 523) (out= 341)(deflated 34%)

运行时指定入口点

1
2
3
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree/out]
└─# java -cp Main.jar top.dustball.Main
[vader,sjh]

依赖jar包

现在jar包中有一个top.dustball.User类,在Test类中调用之

在Test路径下有一个Main.jar这个包,还有测试类Test

1
2
3
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/Test]
└─# ls
Main.jar Test.java

其中Test.java试图调用User类

1
2
3
4
5
6
7
8
9
10
11
12
package Test;

import top.dustball.Dao.User;

public class Test {
public static void main(String[] args) {
User user = new User("vader", "sjh");

System.out.println(user);
}
}

编译链接要加上-cp选项,指定依赖项

1
2
--class-path <path>, -classpath <path>, -cp <path>
Specify where to find user class files and annotation processors
1
PS C:\Users\86135\Desktop\empire\Test> javac Test.java -cp Main.jar

由于Test.java有一个包Test,因此需要退到上级目录执行

1
2
3
PS C:\Users\86135\Desktop\empire\Test> cd ..
PS C:\Users\86135\Desktop\empire> java Test/Test
[vader,sjh]

命令

jar命令

jar命令是jdk的工具,该exe文件在jdk/bin目录下,需要将该bin目录添加到系统环境变量path,也就是配置jdk环境变量

jar -tf

列表列出jar包结构

1
jar -tf a.jar
1
2
3
4
5
6
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# jar -tf Main.jar
META-INF/
META-INF/MANIFEST.MF
out/top/dustball/Main.class
out/top/dustball/Dao/User.class

jar -xf

提取jar包文件,效果等同于解压

1
2
3
4
5
6
7
8
9
10
11
PS C:\Users\86135\Desktop\javacon> jar -xf challange.jar
PS C:\Users\86135\Desktop\javacon> ls

Directory: C:\Users\86135\Desktop\javacon

Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 2018/11/6 13:34 BOOT-INF
d---- 2018/11/6 13:34 META-INF
d---- 2018/11/6 13:34 org
-a--- 2023/1/26 15:21 18134425 challange.jar

javac命令

javac java编译器,用于将源代码java文件编译为class字节码文件

javac可以类比为gcc或者g++

javac -d

指定class文件输出位置,保留包结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# tree src -f
src
└── src/top
└── src/top/dustball
├── src/top/dustball/Dao
│   └── src/top/dustball/Dao/User.java
└── src/top/dustball/Main.java

3 directories, 2 files

┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# javac src/top/dustball/Dao/*.java src/top/dustball/*.java -d out

┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# ls
bin lib out README.md src

┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree]
└─# tree out -f
out
└── out/top
└── out/top/dustball
├── out/top/dustball/Dao
│   └── out/top/dustball/Dao/User.class
└── out/top/dustball/Main.class

3 directories, 2 files

javac -cp

链接外部依赖

1
PS C:\Users\86135\Desktop\empire\Test> javac Test.java -cp Main.jar

java命令

java -jar

执行自带入口点的jar包

java -cp

运行时指定jar包入口点

1
2
3
┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/empire/empiree/out]
└─# java -cp Main.jar top.dustball.Main
[vader,sjh]