一、背景
同事从第三方厂家得到了jar包,但是由于里面的一个java代码逻辑需要自己适配,在拿到源代码以后,修改了一点逻辑,最后编译生成新的class文件。我们暂且把这个文件叫Business.class。
原jar能直接运行,没有任何问题。按照替换的逻辑,我们很快能想到,就是把jar包使用windows解压工具进行解压,然后把class文件放到对应的路径,最后再zip打包为.jar文件即可。但是不出意外,还是出意外了。
通过zip打的包通过java -jar运行报错:
html
HelloWorld.jar中没有主清单属性
仅仅只是替换了个class文件,报这个错误,让人摸不着头脑。下面就是我们的排坑之路~,花了几个小时,最后才搞定。
二、排查过程
1、针对新jar包解压,查看META-INF/MANIFEST.MF
查看了元信息文件,描述的程序入口Main-Class也是正确的。没啥毛病。 因为我们做的操作很简单,就是替换了一个class文件,整个jar都没起来。 那怀疑是通过zip打包的方式可能存在问题
2、旧jar包与新jar包同时解压,使用diff -r 递归对比2个目录看差异
diff -r $old_dir $new_dir 对比了一下,就是我们替换的那个class文件存在的jar包有差异,再继续解压里面的jar,查看文件对比,最终锁定,就只有Hello.class有差异,其它一样。
3、查看jar打包和zip直接打包的区别
通过资料查询到, 通过jar命令打包和zip直接打包,还是存在差别的。 其中存在一个差别就是,jar有一个参数0,0的意思就是只是打包不压缩。 但是可能我们使用windows可视化工具直接修改保存之后,默认就是使用了压缩算法,导致java -jar运行的时候,无法解析那个被压缩的jar文件,导致报错。
4、使用jar cvfM0 Hello.jar *的方式解决问题
使用以下jar打包命令之后,打出的新jar包能正常运行:
bash
jar cvfM0 Hello.jar *
参数解释:
c: 创建一个jar文件
f: 文件名称
v: 显示打包log过程
M: 不创建新的META-INFO和MAIFEST.MF文件,直接使用原项目的
0: 不使用任何压缩算法,只是对打包的文件/目录进行原样打包,不压缩
5、结论验证
为了验证我们的分析结论,通过unzip -v $jar 查看每个文件打包使用的压缩算法,对比下什么情况:
左边是存在文件的jar包(基于旧jar包,重新替换了一个Hello.class文件), 右边是旧jar包(正常运行的)。
内部相同依赖的jar文件 ,我们看到左边采用了压缩算法,压缩了15%的内容。 右边Stored表示没有使用压缩算法,原样打包。
由此可见和我们的猜想是一样的。
三、jar打包命令详解
1、jar --help查看帮助信息
bash
非法选项: -
用法: 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/ .
2、参数详解( 'm', 'f' 和 'e' 标记的指定顺序相同)
1、-c 、-f、-v
-c 表示需要创建一个jar
-f 指明jar包的文件名称
-v 在标准输出中生成详细输出
2、-t 预览jar包内容
bash
jar -tf hello.jar #预览查看hello.jar文件内容
3、-u 添加/更新文件
如果需要更新jar里面的文件,可以直接进行更新。 但是本地目录和jar里面的路径要保持一致(文件覆盖场景)
4、-x解压文件或者全部解压jar
-x可以解压Hello.jar
5、-0 仅存储; 不使用任何 ZIP 压缩
-0 原样添加打包,不会对源文件进行压缩
6、-m、-M
-m 包含指定清单文件中的清单信息(创建META-INF)
-M 不创建条目的清单文件(使用原有的jar的META-INF结构)
7、-C
-C 更改为指定的目录并包含以下文件
3、常用场景指令
1、打包创建jar文件
bash
jar cvf Hello.jar * #常见打包方式
jar cvfM0 Hello.jar * #-0不压缩,打包方式
2、解压jar文件
bash
jar xvf Hello.jar #解压jar包
3、更新文件
bash
jar uf Hello.jar com/bes/Hello.class
4、预览jar内容
bash
jar tf Hello.jar #预览Hello.jar文件内容
5、查看jar压缩详情
bash
unzip -v Hello.jar #查看jar包,打包详情(是否使用压缩、使用哪些压缩算法)
四、总结
建议还是按照java规范,使用jar命令进行打包处理,而不是简单的zip命令进行打包。 但是也不是一概而论,如果先通过zip没问题就可以,如果存在问题回到原点,使用jar打包即可。
特别注意-0参数是否需要,否则会造成上述的问题。