记录一次手动打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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?