☕️【Java 技术之旅】知识盲点关于 jar 包的点点滴滴
每日一句
不同的人生,有不同的幸福。去发现你所拥有幸运,少抱怨上苍的不公,把握属于自己的幸福。你,我,我们大家都可以经历幸福的人生。
背景介绍
经常会头疼于一个 jar 包是如何制作的,包括 maven 的打包方式,springboot 的打 jar 包的原理,jar 包稍稍有错误就会完全无法运行。
压缩 jar 包
大家都知道,java 应用项目按照 JSR 标准协议,一般采用 jar 包进行运行,java 程序可以打包成一个 jar。此外,当然你必须指定一个拥有 main 方法的 main class 类作为 Java 程序的入口,同时它也是启动 JVM 进程的入口
制作只含有字节码文件的 jar 包
我们先来看只含有字节码文件,即只含有 class 文件的 jar 包怎么制作,这是最简单的形式
最简单的 jar 包——直接输出 hello
最终生成的 jar 包结构
方法步骤
用记事本写一个 Hello.java 的文件
用命令行进入到该目录下,编译这个文件
将编译后的 Hello.class 文件打成 jar 包
c(create):表示要创建一个新的 jar 包,
v(view):表示创建的过程中在控制台输出创建过程的一些信息。
f(file)表示给生成的 jar 包命名
运行 jar 包
这时会报如下错误 hello.jar 中没有主清单属性。
添加 Main-Class 属性
用压缩软件打开 hello.jar,会发现里面多了一个 META-INF 文件夹,里面有一个 MANIFEST.MF 的文件,用记事本打开。
在第三行的位置写入 Main-Class: Hello (注意冒号后面有一个空格,整个文件最后有一行空行),保存
再次运行 java -jar hello.jar ,此时成功在控制台看到 hello ,成功
含有两个类的 jar 包——通过调用输出 hello
最终生成的 jar 包结构
方法步骤
用记事本写一个 Hello.java 和一个 Tom.java 的文件,目的是让 Hello 调用 Tom 的 speak 方法
编译: javac Hello.java
此时 Hello.java 和 Tom.java 同时被编译,因为 Hello 中调用了 Tom,在编译 Hello 的过程中发现还需要编译 Tom
打 jar 包,这次我们换一种方式直接定义 Main-Class。
事先准备好上述的 MANIFEST.MF 文件,并存放在 META-INF 文件夹下,此时打 jar 包的命令如下
该命令表示用第一个文件当做 MANIFEST.MF 文件,hello.jar 作为名称,将 Hello.class 和 Tom.class 打成 jar 包。
其中多了一个参数 m,表示要定义 MANIFEST 文件
运行 java -jar hello.jar ,此时成功在控制台看到 hello ,成功
有目录结构的 jar 包——通过引包并调用输出 hello
最终生成的 jar 包结构
我们将上一个稍稍变化一下,将 Tom 这个类放在 com 包下,源文件目录结构变成
同时 Tom.java 需要在第一行声明自己的包名
package com;
Hello.java 需要引入 Tom 这个类,同样要在第一行进行 import
import com.Tom;
方法步骤
编译 Hello.java
打 jar 包,同样准备好 MANIFEST 文件
注意,最后一个 com 表示把 com 这个文件夹下的所有文件都打进 jar 包
运行 java -jar hello.jar ,此时成功在控制台看到 hello ,成功
优化过程
com 包下是有 Tom.java 源文件的,也被打进了 jar 包里,这样不太好,能不能优化一下 javac 命令,使所有的编译后文件编译到另一个隔离的地方呢,答案是可以的。
在编译 Hello.java 时,先新建一个 target 文件夹。然后我们用如下命令:
该命令表示,将所有编译后的文件,都放到 target 文件夹下。
将 META-INF 文件夹也复制到 target 目录下,进入这个目录,输入如下命令
注意最后一个位置变成了*,表示把当前目录下所有文件都打在 jar 包里
至此,我们可以总结出,制作一个只含有 class 字节码文件的 jar 包,以下命令足以
javac 要编译的文件 -d 目标位置
jar -cvfm 命名 MANIFEST 文件 class 类文件 1 class 类文件 2
制作含有 jar 文件的 jar 包
我们将场景稍稍变得复杂一点,看看 jar 包中需要引入其他 jar 包的场景
两个 jar 包间相互调用——调用 jar 外的 jar 输出 hello
最终生成的 jar 包结构
方法步骤
准备:将上述一中写好的那个不带包的 tom.jar 复制过来(目的是调用里面的 speak 方法)
编写一个 Hello.java 并将其编译成 Hello.class,注意,由于 Hello 里面引用了 Tom 类的 speak 方法,因此在打 jar 包时应使用-cp 参数,将 tom.jar 包引入
这里的 -cp 表示 -classpath,指的是把 tom.jar 加入 classpath 路径下
将 hello.class 打成 jar 包,步骤略
此时运行 java -jar 发现报错 ClassNotFoundException:Tom
原因很简单,引入 jar 包需要在 MANIFEST.MF 文件中配置一个新属性:Class-Path,路径指向你需要的所有 jar 包,现在 MANIFEST.MF 这个文件应该变成
修改这个文件,再次运行,发现成功在控制台输出 hello
javac -cp xxx.jar 要编译的文件 -d 目标位置
jar -cvfm 命名 MANIFEST 文件 要打包的文件 1 要打包的文件 2
jar 包中含有 jar 包——调用 jar 内的 jar 输出 hello
最终生成的 jar 包结构
当项目中我们把所需要的第三方 jar 包也打进了我们自己的 jar 包中时,如果仍然按照上述操作方式,会报找不到 Class 异常。原因就是 jar 引用不到放在自己内部的 jar 包。
制作含有资源文件的 jar 包
资源文件在 jar 包内部——读取 jar 内的文件
最终生成的 jar 包结构
方法步骤
资源文件在另一个 jar 包内部——读取另一个 jar 内的文件
最终生成的 jar 包结构
方法步骤
同 1 一样,只不过需要在 MANIFEST 文件中将 resource.jar 加入 classpath
资源文件在 jar 包外部——读取 jar 外的文件
最终生成的 jar 包结构
方法步骤
运行 jar 包
具体的方法是修改 jar 包内目录 META-INF 下的 MANIFEST.MF 文件。
比如有个叫做 demo.jar 的 jar 包,里面有一个拥有 main 方法的 main class:demo.mainClassName
jar 包中有个 MANIFEST.MF 文件,就只要在 MANIFEST.MF 里面添加如下一句话:Main-Class: demo.mainClassName
然后我们可以在控制台里输入 java -jar test.jar 即可以运行这个 jar。
如果这个项目需要引用其他第三方的 jar 包,在 IDE 里面以项目 jar 包的形式引用了这个叫做 some.jar 的包,当时放在项目的 lib 子目录下,最后项目打包时把这个 some.jar 也打进来了,但是用 java -jar 执行这个 test.jar 的时候报找不到 Class 异常,原因就是 jar 引用不到放在自己内部的 jar 包。
classpath 参数设置方式
运行时将其加入 classpath 的方式行不行?就是在运行 jar 的同时加入 classpath 参数。
这种方式是不行的,因为使用 classpath 指定的 jar 是由 AppClassloader(系统型类加载器)来加载,java 命令加了-jar 参数以后,AppClassloader 就只关注 test.jar 范围内的 class 了,classpath 参数失效。
那该怎么引用其他的 jar 包呢?
方法一
Bootstrap Classloader 来加载这些类
我们之前在讲述类加载器的时候也介绍过,可以在运行时使用如下参数:
-Xbootclasspath:完全取代系统 Java classpath.最好不用。
-Xbootclasspath/a::在系统 class 加载后加载。一般用这个。
-Xbootclasspath/p: 在系统 class 加载前加载,注意使用,和系统类冲突就不好了.
win32 java -Xbootclasspath/a: some.jar;some2.jar; -jar test.jar
unix java -Xbootclasspath/a: some.jar:some2.jar: -jar test.jar
win32 系统每个 jar 用分号隔开,unix 系统下用冒号隔开
方法二
使用 Extension Classloader 来加载
你可以把需要加载的 jar 都扔到 %JRE_HOME%/lib/ext 下面,这个目录下的 jar 包会在 Bootstrap Classloader 工作完后由 Extension Classloader 来加载。非常方便,非常省心。
方法三
使用 AppClassloader 来加载,不过不需要 classpath 参数了,我们在 MANIFEST.MF 中添加如下代码:
Class-Path: lib/some.jar
lib 是和 test.jar 同目录的一个子目录,test.jar 要引用的 some.jar 包就在这里面。如果有多个 jar 包需要引用的情况:
Class-Path: lib/some.jar lib/some2.jar
每个单独的 jar 用空格隔开就可以了。注意使用相对路径。
另:如果 META-INF 下包含 INDEX.LIST 文件的话,可能会使 Class-Path 配置失效。INDEX.LIST 是 Jar 打包工具打包时生成的索引文件,删除对运行不产生影响。
版权声明: 本文为 InfoQ 作者【李浩宇/Alex】的原创文章。
原文链接:【http://xie.infoq.cn/article/ef7e6a5913c873de640672155】。文章转载请联系作者。
评论