记录一次手动打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到指定目录)

较为常见的就是cvfecvfm,区别在于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

原文地址:https://www.cnblogs.com/lz2017/p/14398077.html