记录一次手动打jar包过程
一直都有用Maven完成项目的构建,而最近又在学习Gradle构建项目,突然想起自己从来没有手动打过一次jar,便有了本次的尝试与记录
代码结构如下:
projectA目录:com.lynu.service.A.java
projectB目录:cn.lynu.service.B.java
注意这里的A.java和B.java包路径是不同的,主要是想让这两个类在包路径上有些差异
最终目标是将A.java打包成projectA.jar(不可执行只作为依赖);B.java类会使用projectA.jar中的A类,而把B.java打包成可执行的jar(有main方法入口,可使用java -jar或者 java -cp运行)
A.java B.java内容比较简单,只输出一句话,没其他实际意义
package com.lynu.service;
public class A {
public void sayHello() {
System.out.println("jarA方法调用");
}
}
package cn.lynu.service;
import com.lynu.service.A;
public class B {
public static void main(String[] args) {
System.out.println("jarB main方法调用");
new A().sayHello();
}
}
编译A.java
因为A.java比较简单,而且没有依赖其他类或jar,直接通过javac
编译即可
cd projectA
javac com/lynu/service/A.java
这样在projectA/com/lynu.service目录下就会有一个A.class
打包A.java
使用jar
命令进行打包,用法:jar {ctxu}[vfm0Mi] [jar-文件] [manifest-文件]/[main方法所在类] [-C 目录] 文件名 ...
这个命令有一些参数:
-c 创建新档案
-t 列出档案目录
-x 解压jar,从档案中提取指定的 (或所有) 文件
-u 更新现有档案
-v 在标准输出中生成详细输出
-f 指定档案文件名
-m 包含指定清单文件中的清单信息
-n 创建新档案后执行 Pack200 规范化
-e 为捆绑到可执行 jar 文件的独立应用程序指定应用程序入口点
-0 仅存储; 不使用任何 ZIP 压缩
-P 保留文件名中的前导 '/' (绝对路径) 和 ".." (父目录) 组件
-M 不创建条目的清单文件
-i 为指定的 jar 文件生成索引信息
-C 更改为指定的目录并包含其中的文件(可以理解为首先cd到指定目录)
较为常见的就是cvfe
和cvfm
,区别在于e:可以指定一个有main方法的类作为可执行jar的程序入口,而m:可以指定自定义的MANIFEST.MF(清单文件),如果没有指定清单文件打包后会自动生成
因为只打包成不可执行的jar,所以使用cvf即可
jar cvf projectA.jar com/lynu/service/*.java
执行完命令后会在projectA目录下生成projectA.jar
编译B.java
因为B.java中使用了A.java的方法,所以projectA.jar是作为B.java运行的依赖
这是如果只是简单javac编译B.java会出现错误: 程序包com.lynu不存在
或者错误: 找不到符号
的问题,主要是编译时找不到com.lynu.service.A
这个类,这个问题可以通过加入-cp
(-classpath)参数指定所需依赖解决
cd projectB
javac -cp /projectA/projectA.jar com/lynu/service/B.java
类B的class文件也可以正常生成了
打包B.java
需要将B打包成一个可执行jar,就需要指定运行程序的入口,也就是main方法所在
我们使用jar cvfe
命令
jar cvfe projectB.jar cn.lynu.service.B cn/lynu/service/*.java
可以正常在projectB目录下生成jar
执行jar
接下来运行这个可执行jar
java -jar projectB.jar
会发现出现找不到主类
的错误,明明打包的时候指定了主类,怎么会找不到啊?好吧,既然找不到那我们依然加上-cp
参数
java -cp .;/projectA/projectA.jar -jar projectB.jar
会发现可以正常执行jar,再运行下编译后的B.class看看结果
java -cp .;/projectA/projectA.jar cn.lynu.service.B
运行class也没问题,那为什么必须指定-cp
?这是因为不指定classpath的话,projectB.jar根本不知道自己依赖的projectA.jar在什么地方,如果觉得每次执行都得要加上-cp
比较麻烦,可以通过自定义清单文件MANIFEST.MF帮助我们
在projectB目录下添加一个MANIFEST.MF文件,文件内容如下
Manifest-Version: 1.0
Created-By: 1.8.0_201 (Oracle Corporation)
Class-Path: /projectA/projectA.jar
Main-Class: cn.lynu.service.B
Main-Class:程序入口类,类如果有限定名,则写成全限定名,比如package.classname
Class-Path:所依赖的jar,如果有多个jar包,则以空格分隔多个jar包,一行太多会报错line too long,这时需要把Class-Path分多行写。注意:从第二行开始,必须以两个空格开头
文件最后有两行空行不能少
再次打包B.java
jar cvfm projectB.jar ./MANIFEST.MF cn/lynu/service/*.java
重新打的jar就可以直接使用java -jar
运行了
java -jar projectB.jar
总结
在编译B.java时,也在使用-cp
指定projectA.jar的位置,实际上将projectA.jar放到JAVA_HOME/jre/lib/ext
目录下就不需要显式指定位置了,因为类加载器(ExtClassLoader)会自动加载这个目录下的所有jar