开发基础 | Java 打包方式总结

admin 2024年11月10日16:04:45评论13 views字数 9699阅读32分19秒阅读模式

Java 打包方式总结

前言

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

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

原生命令行打包

基本介绍

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

开发基础 | Java 打包方式总结

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

C:UsersAdministrator>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:UsersAdministratorDesktopMyJavaProject> tree /F ./
C:USERSADMINISTRATORDESKTOPMYJAVAPROJECT
Project1.java
Project2.java

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

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

随后我们将其编译:

开发基础 | Java 打包方式总结

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

PS C:UsersAdministratorDesktopMyJavaProject> 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:UsersAdministratorDesktopMyJavaProject> java -cp .classes.jar Project1
Project1 Hello~
PS C:UsersAdministratorDesktopMyJavaProject> java -cp .classes.jar Project2
Project2 Hello~
PS C:UsersAdministratorDesktopMyJavaProject> java -jar classes.jar
classes.jar中没有主清单属性

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

开发基础 | Java 打包方式总结

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

结果如下:

开发基础 | Java 打包方式总结

示例2打包方式

批量编译小脚本

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

开发基础 | 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=out

for /r %cd%/%PACKAGE_NAME%/ %%i in (*.java) do javac %%i -d %cd%/%WRITE_PATH% -encoding utf-8 -cp %cd%;lib/*
endlocal

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

开发基础 | Java 打包方式总结

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

将编译好的 class 进行打包

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

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

目录结构如下:

开发基础 | Java 打包方式总结

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

PS C:UsersAdministratorDesktopMyJavaProject> 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:

开发基础 | Java 打包方式总结

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;
    }
}

而我们知道的是, IDEAjar包中是这样配置的:

开发基础 | Java 打包方式总结

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

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

通过清单链接

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

开发基础 | Java 打包方式总结

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

开发基础 | Java 打包方式总结

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

开发基础 | Java 打包方式总结

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

提取到目标 JAR

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

开发基础 | Java 打包方式总结

这里我们也可以看到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>

随后打包过程如下:

开发基础 | Java 打包方式总结

随后可以看到的是, 并没有将依赖包打入进来, 当然, 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相同, 最终打包结果如下:

开发基础 | Java 打包方式总结

其效果与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相同, 最终打包结果如下:

开发基础 | Java 打包方式总结

SpringBoot 打包

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

梭哈即可.

Ending...

需要时直接Ctrl C V了, 一次总结, 终身使用. 😄, 面向笔记编程.

原文始发于微信公众号(Heihu Share):开发基础 | Java 打包方式总结

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月10日16:04:45
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   开发基础 | Java 打包方式总结https://cn-sec.com/archives/3378288.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息