记录一次手动打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内容比较简单,只输出一句话,没其他实际意义

Copy
package com.lynu.service; public class A { public void sayHello() { System.out.println("jarA方法调用"); } }
Copy
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编译即可

Copy
cd projectA javac com/lynu/service/A.java

这样在projectA/com/lynu.service目录下就会有一个A.class

打包A.java#

使用jar命令进行打包,用法:jar {ctxu}[vfm0Mi] [jar-文件] [manifest-文件]/[main方法所在类] [-C 目录] 文件名 ...

这个命令有一些参数:

Copy
-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即可

Copy
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)参数指定所需依赖解决

Copy
cd projectB javac -cp /projectA/projectA.jar com/lynu/service/B.java

类B的class文件也可以正常生成了

打包B.java#

需要将B打包成一个可执行jar,就需要指定运行程序的入口,也就是main方法所在

我们使用jar cvfe命令

Copy
jar cvfe projectB.jar cn.lynu.service.B cn/lynu/service/*.java

可以正常在projectB目录下生成jar

执行jar#

接下来运行这个可执行jar

Copy
java -jar projectB.jar

会发现出现找不到主类的错误,明明打包的时候指定了主类,怎么会找不到啊?好吧,既然找不到那我们依然加上-cp参数

Copy
java -cp .;/projectA/projectA.jar -jar projectB.jar

会发现可以正常执行jar,再运行下编译后的B.class看看结果

Copy
java -cp .;/projectA/projectA.jar cn.lynu.service.B

运行class也没问题,那为什么必须指定-cp?这是因为不指定classpath的话,projectB.jar根本不知道自己依赖的projectA.jar在什么地方,如果觉得每次执行都得要加上-cp比较麻烦,可以通过自定义清单文件MANIFEST.MF帮助我们

在projectB目录下添加一个MANIFEST.MF文件,文件内容如下

Copy
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

Copy
jar cvfm projectB.jar ./MANIFEST.MF cn/lynu/service/*.java

重新打的jar就可以直接使用java -jar运行了

Copy
java -jar projectB.jar

总结#

在编译B.java时,也在使用-cp指定projectA.jar的位置,实际上将projectA.jar放到JAVA_HOME/jre/lib/ext目录下就不需要显式指定位置了,因为类加载器(ExtClassLoader)会自动加载这个目录下的所有jar

posted @   OverZeal  阅读(1243)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示

目录

目录

X