Create an Executable Fat JAR With Your Command Line
本文是我的博客文章的统一文章,回顾了在不使用任何其他插件,IDE或任何其他工具(仅使用纯命令行和Java)的情况下,可以在Java中创建胖JAR(Java存档文件)的可能性。
在构建工具(Ant,Maven或Gradle)的世界中,考虑命令行似乎甚至没有用。 最著名的IDE(IntelliJ,Eclipse或NetBeans)立即提供构建工具和实现。 但是,假设您只有命令行,没有Internet访问。
那你会怎么做-
第1部分:编译ExecutableOne.jar(GitHub)
第一部分的目的是创建一个可执行的JAR文件。 我们称它为ExecutableOne.jar。 打开命令行,首先创建一个简单的项目文件夹:executable-one。 示例项目结构遵循Maven标准目录布局结构。
1 2 3 4 5 6 7 8 9 10 | ./libs ./out ./README.md ./src ./src/main ./src/main/java ./src/main/java/com ./src/main/java/com/exec ./src/main/java/com/exec/one ./src/main/resources |
由于我们的目的是创建可执行的JAR文件,因此我们必须创建一个主类。 让我们在com.exec.one包中进行操作。 该包可以在我们的示例项目结构的文件夹SRC / MAIN / JAVA中找到。
1 2 3 4 5 6 7 |
在文件夹SRC / MAIN / RESOURCES中,创建META-INF文件夹,然后在其中将MANIFEST.FM文件放置在其中。 让我们打开新创建的MANIFEST.FM文件并进行基本描述。
1 2 3 | Manifest-Version: 1.0 Class-Path: . Main-Class: com.exec.one.Main |
注意:每个JAR文件只有一个MANIFEST.FM文件。
MANIFEST.FM文件包含有关如何使用JAR文件的详细信息。 我们不会深入探讨细节-让我们专注于已定义的选项。
Manifest-Version:清单文件版本。
Manifest-Version:清单文件版本。
类路径:应用程序或扩展类加载器使用此属性的值来构造其内部搜索路径。 最初,类加载器下载并打开其搜索路径中的每个元素。 为了这些目的,已经使用了简单的线性搜索算法。
Main-Class:存在启动器将在启动时加载的类的名称。
现在,我们创建没有任何* .jar库的JAR文件。 我们项目结构中的LIBS文件夹仍然为空。 为此,我们需要首先使用javac编译项目。 同时,我们会将输出存储在OUT文件夹中。 让我们回到命令行,然后在项目的根目录中键入:
1 | $javac -cp ./src/main/java ./src/main/java/com/exec/one/*.java -d ./out/ |
该项目已被编译到OUT目录中。 您可以使用ls命令进行检查。
1 | $jar cvfm ExecutableOne.jar ./src/main/resources/META-INF/MANIFEST.MF -C ./out/ . |
让我们简要回顾/解释一下我们使用的JAR工具选项:
c:表示我们要创建一个新的JAR文件。
c:表示我们要创建一个新的JAR文件。
v:将详细输出生成为标准输出。
f:指定要创建的jarfile。
m:表示我们使用的清单文件。 清单文件包含名称-值对。
-C:指示目录的临时更改。 类从该目录添加到JAR文件。 点表示所有类(文件)。
对于最终输出,请打开命令行并键入:
1 2 3 4 | $java -jar ./ExecutableOne.jar standard output: Main Class Start |
做得好! 让我们进入第2部分。
第2部分:使用其他程序包编译ExecutableOne.jar
本部分的主要目的是向您展示如何编译包含附加软件包的可执行JAR文件。 为此,我们创建了MagicService。 该服务为我们提供了getMessage()方法,并将消息输出到标准输出。
让我们打开命令行,并使用文件MagicService.java创建一个新文件夹SERVICE:
1 2 | $mkdir src/main/java/com/exec/one/service $vi src/main/java/com/exec/one/service/MagicService.java |
可以在以下示例中使用新创建的MagicService:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
MagicService与Main类位于包结构中的不同位置。 现在,我们回到Main类并导入新创建的MagicService。 在导入和服务实例化之后,Main类将接收对getMessage()方法的访问。 Main类将以以下方式更改。
1 2 3 4 5 6 7 8 9 10 11 |
现在,我们可以编写代码了。 让我们回到命令行,进入Executable-One项目的根文件夹。 第一步是将Executable-One项目编译/重新编译到OUT文件夹中。 为此,我们需要添加新创建的类MagicService.java的位置。
1 | javac -cp ./src/main/java ./src/main/java/com/exec/one/*.java ./src/main/java/com/exec/one/**/*.java -d ./out/ |
第二步是从编译的类中创建一个可执行的JAR文件。 因为我们没有更改JAR文件逻辑,所以我们不需要对命令进行任何更改。 这意味着MANIFEST.FM文件保持原样,没有任何更改:
1 2 3 | 1 Manifest-Version: 1.0 2 Class-Path: . 3 Main-Class: com.exec.one.Main |
现在,我们可以再次在示例项目的根目录中执行类似于第1部分的命令:
1 | jar cvfm ExecutableOne.jar ./src/main/resources/META-INF/MANIFEST.MF -C ./out/ . |
通过执行创建的JAR文件,我们获得打印到标准输出中的消息。
1 2 3 4 | $java -jar ExecutableOne.jar output: Main Class Start MESSAGE : Magic Message |
恭喜你! 再好!
第3部分:创建可执行的Fat JAR(GitHub)
这部分的目的是创建一个胖的JAR(Java存档)文件,其中包含已开发程序的所有必要依赖项。 作为外部库,我们需要使用在第2部分中创建的JAR文件。在第3部分中,我们创建了一个新的示例项目,名为" Executable-Two"(您可以从上面的链接下载)。
两个可执行文件项目具有以下文件夹结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ./libs ./libs/ExecutableOne.jar ./out ./README.md ./src ./src/main ./src/main/java ./src/main/java/com ./src/main/java/com/exec ./src/main/java/com/exec/two ./src/main/java/com/exec/two/Main.java ./src/main/resources ./src/main/resources/META-INF ./src/main/resources/META-INF/MANIFEST.MF |
LIBS文件夹包含先前创建的JAR文件" ExecutableOne.jar"。" ExecutableOne.jar"包含MagicService类,我们将在" ExecutableTwo"内部使用该类。 我们将实例化类MagicService并执行公共方法getMessage()。 所有这些都将在项目" ExecutableTwo"的Main类中进行。
让我们在项目包com.exec.two中创建以下Main类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package com.exec.two; import com.exec.one.service.MagicService; public class Main { public static void main(String[] args){ System.out.println("Executable-Two Main"); MagicService service = new MagicService(); System.out.println("MagicService from Executable-ONE"); System.out.println("MESSAGE:" + service.getMessage()); } } |
现在,我们已经为创建胖JAR文件做好了一切准备。 我们从先前创建的JAR库中导入了MagicService,并执行了它的getMessage()方法。 在接下来的几个步骤中,我们将使用Java JDK提供的javac和JAR工具。 让我们返回命令行并编译项目。 在命令中,我们需要通知编译器我们应该将其类路径扩展到使用的库。
1 2 3 | $javac -cp ./src/main/java ./src/main/java/com/exec/two/*.java -d ./out/ -classpath ./libs/ExecutableOne.jar |
=" LINE-HEIGHT:18px; WIDOWS:2; TEXT-TRANSFORM:none; Background-COLOR:rgb(255,255,255); FONT-STYLE:"可执行文件二"项目已成功编译到OUT目录中。 正常;文本索引:0px;白色空间:正常; ORPHANS:2;字母间距:正常;颜色:rgb(102,102,102);字体大小:13px;文字间距:0px; -webkit-text-stroke- 宽度:0像素; font-variant-caps:正常; font-variant-ligatures:正常">现在是时候适当地准备OUT目录以创建胖JAR了。 在OUT目录中,我们具有为" Executable-Two"创建的已编译类。 同时,JAR工具仅读取物理上位于文件系统上的文件,而不会读取压缩的JAR文件。 当然,这意味着JAR工具不会解压缩并读取OUT目录中的任何* .jar文件。
结果是,即使我们将ExecutableOne.jar复制到OUT目录中,JAR工具也不会解压缩ExecutableOne.jar文件,而是将库(以其压缩形式)添加到结果中。 当然,由于它是压缩的,因此将被忽略。
问题在于$ java -jar工具无法读取内部打包的* .jar存档文件!
这意味着我们需要将先前创建的Java存档(JAR)" Executable-One.jar"解压缩到" Executable-Two"项目的OUT目录中。打开命令行并键入:
1 2 3 4 | $cp libs/ExecutableOne.jar ./out/ $cd ./out $tar xf ExecutableOne.jar $rm ExecutableOne.jar |
现在,"可执行文件二"项目输出目录已准备好用作新JAR文件的源文件夹。
注意:在每个可执行JAR文件中,只有一个MANIFEST.FM文件可用。
要将"可执行文件二"项目打包到JAR归档文件中,我们使用位于文件夹./src/main/resources/META-INF/中的新创建的清单文件:
1 2 3 | Manifest-Version: 1.0 Class-Path: . Main-Class: com.exec.two.Main |
现在,我们可以通过键入以下内容将所有内容打包在一起:
1 | $jar cvfm ExecutableTwo.jar ./src/main/resources/META-INF/MANIFEST.FM -C./out/ . |
当我们执行新创建的胖JAR文件" ExecutableTwo.jar"时,我们将收到以下输出。
1 2 3 4 5 | $java -jar ./ExecutableTwo.jar output: Executable-Two Main MagicService from Executable-ONE MESSAGE: Magic Message |
恭喜你! 您已经执行了繁琐的JAR文件!