使用命令行创建可执行的Fat JAR

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
package com.exec.one;

public class Main {
    public static void main(String[] args){                                                                                                                                            
        System.out.println("Main Class Start");                                                                                                                                        
    }                                                                                                                                                                                  
}

在文件夹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
    package com.exec.one.service;                                                                                                                                                          

    public class MagicService {                                                                                                                                                            

      private final String message;                                                                                                                                                      
        public MagicService(){                                                                                                                                                            
            this.message ="Magic Message";                                                                                                                                                
        }                                                                                                                                                                                  

        public String getMessage(){                                                                                                                                                        
             return message;                                                                                                                                                                
        }                                                                                                                                                                                  

    }

    MagicService与Main类位于包结构中的不同位置。 现在,我们回到Main类并导入新创建的MagicService。 在导入和服务实例化之后,Main类将接收对getMessage()方法的访问。 Main类将以以下方式更改。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.exec.one;                                                                                                                                                                  

    import com.exec.one.service.MagicService;                                                                                                                                              

    public class Main {                                                                                                                                                                    
        public static void main(String[] args){                                                                                                                                            
            System.out.println("Main Class Start");                                                                                                                                        
            MagicService service = new MagicService();                                                                                                                                    
            System.out.println("MESSAGE :" + service.getMessage());                                                                                                                      
         }                                                                                                                                                                                  
    }

    现在,我们可以编写代码了。 让我们回到命令行,进入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文件!