【java开发】 Java 打包方式总结

news/2024/11/14 8:04:44/文章来源:https://www.cnblogs.com/o-O-oO/p/18537560

前言

由于笔者之前也一直在使用 IDEA, Maven 等成熟工具|框架的打包方式, 也没有仔细研究过这个 JAR 包打包之中的细节, 网上公开的视频也没有找到, 但文章倒挺多的, 那周六日就简单看一下吧, 将这些打包方式都整理整理.

本篇文章彻底理解 Maven & IDEA & 原生的打包方式, 妈妈再也不用担心我不懂 jar 了.

原生命令行打包

基本介绍

安装完毕的JDK_HOME/bin目录下, 存在jar.exe文件, 如下:

当我们通过命令行进行调用时, 会给出提示信息:

C:\Users\Administrator>jar
用法: 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/ .

最后两行提示的示例1 & 示例2, 是两种不同的打包方式。
下面用土白话说明一下.

示例1: 不使用自定义的 MANIFEST 文件进行打包, 这种方式生成的jar包不能使用java -jar XXX.jar命令执行, 因为不是自定义的 MANIFEST 文件, 所以不存在程序入口. (程序入口需要自定义配置 MANIFEST 文件, 并在其进行指明)

示例2: 比较常用的打包方式, 使用自定义的 MANIFEST 文件参与打包, 这样能够实现往包中添加依赖, 并且可以指定程序入口, 实现java -jar XXX.jar 直接运行jar包.

示例1打包方式

我们准备如下项目结构:

PS C:\Users\Administrator\Desktop\MyJavaProject> tree /F ./
C:\USERS\ADMINISTRATOR\DESKTOP\MYJAVAPROJECTProject1.javaProject2.java

上述准备了两个简单的Java文件, 内容如下:

public class Project1 {public static void main(String[] args){System.out.println("Project1 Hello~");}
}
// Project2 与 Project1 程序逻辑相同, 简单输出内容即可

随后我们将其编译:

进行简单编译完成后, 我们可以使用jar.exe - 示例1进行打包, 如下:

PS C:\Users\Administrator\Desktop\MyJavaProject> jar cvf classes.jar Project1.class Project2.class
已添加清单
正在添加: Project1.class(输入 = 425) (输出 = 289)(压缩了 32%)
正在添加: Project2.class(输入 = 425) (输出 = 289)(压缩了 32%)

将其进行打包后, 我们进行运行其中的Project1 & Project2, 以及看一下java -jar classes.jar的运行结果:

PS C:\Users\Administrator\Desktop\MyJavaProject> java -cp .\classes.jar Project1
Project1 Hello~
PS C:\Users\Administrator\Desktop\MyJavaProject> java -cp .\classes.jar Project2
Project2 Hello~
PS C:\Users\Administrator\Desktop\MyJavaProject> java -jar classes.jar

classes.jar中没有主清单属性

这里java -jar classes.jar说没有主清单属性, 其原因则是因为META-INF/MANIFEST.MF中没有指明程序入口.

java -cp classes.jar 类名可运行成功, 本质上是执行的该jar包下的具体类::main方法.

我们也可以直接通过修改classes.jar!/META-INF/MANIFEST.MF文件, 通过配置Main-Class来配置我们的程序入口:

Manifest-Version: 1.0
Created-By: 1.8.0_131 (Oracle Corporation)
Main-Class: Project1

结果如下:

示例2打包方式

批量编译小脚本

同样还是test1.javatest2.java以及Main.java但是各自有自己的包名, 下面看一下即将创建的目录结构:

Project1.java:

package cn.heihu577;public class Project1 {public static void main(String[] args){System.out.println("Heihu577~");}public void sayHello(){System.out.println("[Project1] sayHello");}
}

Project2.java:

package cn.helen;public class Project2 {public static void main(String[] args){System.out.println("Helen~");}public void sayHello(){System.out.println("[Project2] sayHello");}
}

mymain.java:

package cn;import cn.heihu577.Project1;
import cn.helen.Project2;public class mymain {public static void main(String[] args){System.out.println("[mymain] main");Project1 project1 = new Project1();Project2 project2 = new Project2();project1.sayHello();project2.sayHello();}
}

随后创建out目录, 创建完毕后创建批量编译脚本:

:: 运行本脚本, 一定要放在项目根目录, 比如当前目录下有cn包, 一定放到cn包同级, 因为使用了 %cd%
:: 这里需要指明要编译的包名
setlocal
set PACKAGE_NAME=cn
set WRITE_PATH=outfor /r %cd%/%PACKAGE_NAME%/ %%i in (*.java) do javac %%i -d %cd%/%WRITE_PATH% -encoding utf-8 -cp %cd%;lib/*
endlocal

随后在项目根目录进行运行脚本, 将会批量编译java:

最终将编译好的所有class文件都存放至out目录中.

将编译好的 class 进行打包

这里我们需要创建out/META-INF/MANIFEST.MF文件, 用来指明我们程序入口:

Manifest-Version: 1.0
Main-Class: cn.mymain

目录结构如下:

最终我们进行执行示例2的打包方式:

PS C:\Users\Administrator\Desktop\MyJavaProject> jar cvfm test.jar out/META-INF/MANIFEST.MF -C out/ .
已添加清单
正在添加: cn/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: cn/heihu577/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: cn/heihu577/Project1.class(输入 = 518) (输出 = 329)(压缩了 36%)
正在添加: cn/helen/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: cn/helen/Project2.class(输入 = 512) (输出 = 324)(压缩了 36%)
正在添加: cn/mymain.class(输入 = 547) (输出 = 371)(压缩了 32%)
正在忽略条目META-INF/
正在忽略条目META-INF/MANIFEST.MF

最终我们可以看到打包的结果, 并可以通过-jar进行运行刚刚生成的jar:

IDEA 编辑器打包

我们知道的是, IDEA 编辑器提供了打包方式, 下面我们来看看这里打包方式是如何使用的.

而上面由于我们知道了, 整个打包是围绕MANIFEST.MF这个文件进行打包的, 上面的案例是我们自己创建的MANIFEST.MF文件, 而IDEA中打包会帮助我们生成MANIFEST.MF文件.

基本环境搭建

这里我们在IDEA中创建一个基本环境, 用于主要测试。
首先pom.xml文件如下:

<dependencies><dependency><groupId>commons-beanutils</groupId><artifactId>commons-beanutils</artifactId><version>1.9.4</version></dependency>
</dependencies>

引入beanutils包则是方便我们后续对打包带依赖 | 不带依赖所生成的MANIFEST.MF文件进行理解.

随后创建两个基本的测试类:

package com.heihu577;import org.apache.commons.beanutils.BeanUtils;public class sayHello {public static void main(String[] args) throws Exception {Person person = new Person();BeanUtils.setProperty(person, "name", "heihu577");System.out.println(BeanUtils.getProperty(person, "name"));}
}

Person类代码如下:

package com.heihu577;public class Person {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}

而我们知道的是, IDEA打jar包中是这样配置的:

而这里有两个选项, 分别是提取到目标 JAR && 复制到输出目录并通过清单链接, 这两种方式有什么区别呢?

注意: 在实际通过 IDEA 打 JAR 包时, 一定要指明主类.

通过清单链接

这里所说的通过清单链接其实是在说: 将库中的BeanUtils.jar, 以及当前项目的jar, 分别导出出来, 然后当前项目的jar通过MANIFEST.MF进行指明ClassPath来进行链接, 说这么多意义也不大, 直接上图片.

这里可以看到的是, 导出的是四个jar包, 而由于我们的PackageStudy01.jar依赖于BeanUtils.jar, 所以它的/META-INF/MANIFEST.MF文件中的内容是这样的:

可以从中看到的是, PackageStudy01.jar是可以成功运行的. 而如果我们将生成的这四个jar包中, 将beanutils.jar以及它相关的jar都删掉, 只留下PackageStudy01.jar, 运行是否会爆出类加载不上异常?

可以看到的是, jar 与 jar 之间的依赖也是通过Class-Path进行指明的.

提取到目标 JAR

而提取到目标JAR则比较暴力, 将当前项目以及所需要的依赖, 都打到一个文件里面, 直接放上效果图:

这里我们也可以看到MANIFEST.MF中, 是不需要通过Class-Path进行指明的. 因为都在同一个包下.

Maven 打包

关于Maven打包, 都是使用提供好的插件进行打包, 而Maven打包的插件也是比较多的, 这里笔者主要来介绍一下它们之间的选择, 与区别。

maven-jar-plugin [不带依赖打包]

pom.xml文件中进行引入:

<build><finalName>${project.artifactId}</finalName><!--修改编译出来的jar包名,仅为{artifactId}.jar--><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.2.0</version><configuration><archive><manifestFile>${project.basedir}/src/main/resources/META-INF/MANIFEST.MF</manifestFile>
<!--                        <manifest>-->
<!--                            <addClasspath>true</addClasspath>-->
<!--                            <mainClass>com.leon.Main</mainClass>-->
<!--                        </manifest>--></archive></configuration></plugin></plugins>
</build>

这里archive标签, 我们可以进行定义manifest的参数, 也可以通过引入本地MANIFEST.MF文件, 在这里我们就使用本地引入文件的方式来举例吧。
这里MANIFEST.MF文件内容定义如下:

Manifest-Version: 1.0
Main-Class: com.heihu577.sayHello

如果爆红, 我们只需要使用dependency引入一下即可:

<dependencies><dependency><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.1.2</version></dependency>
</dependencies>

随后打包过程如下:

随后可以看到的是, 并没有将依赖包打入进来, 当然, META-INF/MANIFEST.MF文件中是不存在Class-Path的, 使用该插件只能进行打包无依赖的jar.
maven-dependency-plugin [通过清单链接]

但是maven-jar-plugin可以通过配合maven-dependency-plugin达到IDEA中通过清单链接的效果, 它的配置如下:

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-dependency-plugin</artifactId><executions><execution><id>copy-dependencies</id><phase>prepare-package</phase><goals><goal>copy-dependencies</goal></goals><configuration><outputDirectory>${project.build.directory}/lib</outputDirectory><!-- 将带有依赖的 jar 放入到 lib 目录中 --><overWriteReleases>false</overWriteReleases><overWriteSnapshots>false</overWriteSnapshots><overWriteIfNewer>true</overWriteIfNewer></configuration></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><configuration><archive><manifest><addClasspath>true</addClasspath><classpathPrefix>lib/</classpathPrefix><!-- 配置 Class-Path, 目录与上面 outputDirectory 保持一致即可 --><mainClass>com.heihu577.sayHello</mainClass> <!-- 放置自己的入口程序 --></manifest></archive></configuration></plugin></plugins>
</build>

其操作步骤与maven-jar-plugin相同, 最终打包结果如下:

其效果与IDEA中打包的通过清单链接的功能效果是一致的.

maven-assembly-plugin [带依赖打包]

使用该插件进行打包将带有依赖, 配置如下:

<build><finalName>${project.artifactId}</finalName><plugins><plugin><artifactId>maven-assembly-plugin</artifactId><configuration><archive><manifestFile>${project.basedir}/src/main/resources/META-INF/MANIFEST.MF</manifestFile><!-- 需本地创建 /META-INF/MANIFEST.MF 文件 --></archive><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs></configuration><executions><execution><id>make-assembly</id> <!-- this is used for inheritance merges --><phase>package</phase> <!-- bind to the packaging phase --><goals><goal>single</goal></goals></execution></executions></plugin></plugins>
</build>

其操作步骤与maven-jar-plugin相同, 最终打包结果如下:

SpringBoot 打包

<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins>
</build>

梭哈即可.
Ending...

原创 Heihu577 Heihu Share

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/829595.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分享一套基于thinkphp开发的小说内容管理系统源码,附安装教程,100%开源。

小说内容管理系统系统介绍小说内容管理系统是一套用于快速构建小说发布与阅读的小说内容管理平台。使用小说内容管理系统可以快速搭建一个完整的小说阅读网站,节省开发时间和成本。 小说内容管理系统是一套基于ThinkPHP6 + MySql + Layui + BUI 开发的小说行业内容管理系统。 …

【数据库】Cassandra的安装及基本操作

Cassandra 数据库安装 官方文档 安装环境Ubuntu 22.04.5LTS Cassandra 4.0.14 JDK 11操作步骤下载安装包curl -OL https://dlcdn.apache.org/cassandra/4.0.14/apache-cassandra-4.0.14-bin.tar.gz解压操作解压tar -zxvf ./apache-cassandra-4.0.14-bin.tar.gz运行数据库,该步…

【数据库】GeoMesa的安装及基本操作

GeoMesa-Cassandra 安装 官方文档_安装 GeoMesa Cassandra 安装环境Ubuntu 22.04.5 LTS Cassandra 3.11.16 JDK 11 geomesa-cassandra_2.12-5.1.0操作步骤下载 bin文件 wget https://github.com/locationtech/geomesa/releases/download/geomesa-5.1.0/geomesa-cassandra_2.12-…

【数据库】GeoServer的安装及基本操作

GeoServer 安装 参考: WSL-Ubuntu22.04 安装Geoserver流程 方式一:采用Tomcat的方式安装GeoServer 下载curl -OL https://sourceforge.net/projects/geoserver/files/GeoServer/2.26.0/geoserver-2.26.0-war.ziphttp://localhost:8080/http://localhost:8080/geoserver/方式二…

【java开发】 java web的Filter(过滤器),Interceptor(拦截器) 和 Aspect(切面)

“ 在Java Web开发中,始终离不开Filter,Interceptor,Aspect三个部分,本文将对此进行介绍。” Filter 是servlet层面的,由Servlet容器(如Tomcat)支持,只能在web程序中使用,实现了javax.servlet.Filter接口 Interceptor 是Spring Web层面的(Structs也有), 它是由Spring容器…

Tesla Model Y refresh All In One

Tesla Model Y refresh All In One Tesla Model Y Juniper / Tesla Model Y 焕新版Tesla Model Y refresh All In OneTesla Model Y Juniper / Tesla Model Y 焕新版demoshttps://www.youtube.com/watch?v=WWou0YzxJ7Yhttps://topelectricsuv.com/news/tesla/2024-tesla-model…

【开源系列】Loki 与 Promtail 实现轻量级日志管理系统

1、简介 Grafana Loki 是一个用于日志聚合和分析的开源工具,专为云原生环境设计,与 Grafana 无缝集成,能够高效地收集、存储和查询日志。以下是如何使用 Grafana Loki 收集日志的详细步骤。 2、环境准备 确保你已经安装了以下组件: Docker(用于容器化部署)Grafana(用于可…

【学习软件】【疯狂地理Beta1.0.0】

【前言】 近期,使用Unity开发了一款有关中国地理知识的App,目前仅支持Android,用于自己学习。知识要活学活用才行。【软件截图】 【下载地址】 https://rere.lanzoue.com/iiblf2eochmh密码:5jvy【敬畏能量 敬畏自然】

Groove Intermediate pg walkthrough

80端口web站点 dirsearch 没发现啥有用信息 感觉就是让我们突破登录框进后台的 https://github.com/ChurchCRM/CRM/issues/137 上网查到默认密码 登录后台跟具cms查exp发现有个SQL注入 payload找半天找到一个 可以直接sql注入 http://192.168.167.44/EventAttendance.php?Act…

easyre 1

easyre 1 下载文件解压发现是个exe文件直接丢进IDA分析flag{this_Is_a_EaSyRe}本文来自博客园,作者:TazmiDev,转载请注明原文链接:https://www.cnblogs.com/tazmi/p/18537488

AtCoder Beginner Contest 379

A - Cyclic 题意输入\(3\)个连续字符\(a,b,c\),输出另外两种顺序。思路模拟。代码点击查看代码 #include<bits/stdc++.h> using namespace std; #define int long long typedef pair<int, int> pii;const int mxn = 1e6 + 5;void solve() {char a, b, c;cin >&…

[NPUCTF2020]ReadlezPHP

打开靶机,看看情况右键想看源代码没反应,关掉设置里的JavaScript即可查看源代码 点进去看看发现源码<?php #error_reporting(0); class HelloPhp {public $a;public $b;public function __construct(){$this->a = "Y-m-d h:i:s";$this->b = "date&q…