java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java可执行JAR包打包

Java可执行JAR包打包三种方式完全解析

作者:常利兵

在Java开发中我们经常使用JAR文件来打包应用程序及其依赖,这个过程涉及到许多细节,尤其是在项目中包含其他JAR文件时,这篇文章主要介绍了Java可执行JAR包打包三种方式完全解析的相关资料,需要的朋友可以参考下

引言:JAR 包打包的困扰与重要性

写完代码只是万里长征的第一步,如何将代码打包成一个 “开箱即用” 的 JAR 文件,才是交付的关键一步。不少 Java 开发者都有过这样的经历:本地运行得好好的程序,兴高采烈地打包后,满心欢喜地准备部署,结果一运行,报错ClassNotFoundException!这时候先别慌,大概率不是代码出了岔子,而是 JAR 包没打好。

在 Java 开发中,JAR(Java Archive)包是一种非常常见的文件格式,它可以将多个 Java 类文件及其相关元数据和资源(如图像和库)打包成单一文件,方便分发和部署。而对于 Maven 项目来说,打可执行 JAR 包有多种方式。今天,我们就来深入对比三种主流方案:maven-jar-plugin(轻量外置依赖)、maven-assembly-plugin(全家桶打包)和 maven-shade-plugin(高级防冲突版)。每种方式都会附上真实的 pom.xml 配置、执行命令以及输出结构,让大家看完就能轻松上手。

方式一:maven-jar-plugin,轻量但依赖外置

原理剖析

maven-jar-plugin 是 Maven 的一个内置插件,主要用于将项目编译后的 class 文件及相关资源打包成 JAR 文件。不过,它打包时仅包含项目自身的代码和资源,第三方依赖则不会被打包进去。那运行时如何找到这些依赖呢?它通过在 MANIFEST.MF 文件中指定依赖的路径,让 JVM 在运行时能从指定位置加载依赖。就好比你搬家时,只带走了自己的行李,而家具等大件物品则在新家附近的仓库放着,然后在入住清单上写清楚了仓库的位置,这样入住时就能顺利取到家具。

pom.xml 配置详解

pom.xml 文件中,需要进行如下配置:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.2.0</version>
            <configuration>
                <archive>
                    <manifest>
                        <mainClass>org.example.App</mainClass>
                        <addClasspath>true</addClasspath>
                        <classpathPrefix>dependencies/</classpathPrefix>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <version>3.1.1</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>${project.build.directory}/dependencies/</outputDirectory>
                        <includeScope>runtime</includeScope>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

配置解释如下:

打包后结构与执行命令

执行 mvn clean package 命令后,在 target 目录下会生成项目的 JAR 包以及 dependencies 目录,dependencies 目录中存放着项目的所有第三方依赖包。
JAR 包解压后,目录结构大致如下:

├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│       └── org.example
│           └── java-demo
│               ├── pom.properties
│               └── pom.xml
└── org
    └── example
        └── App.class

MANIFEST.MF 文件内容类似:

Manifest-Version: 1.0
Created-By: Maven Jar Plugin 3.2.0
Build-Jdk-Spec: 17
Class-Path: dependencies/fastjson2-2.0.60.jar 第三方依赖包在这里
Main-Class: org.example.App                         启动类

执行命令为:

java -jar java-demo-1.0-SNAPSHOT.jar

运行时,JVM 会根据 MANIFEST.MF 文件中 Class-Path 指定的路径去加载依赖包。

优缺点分析

方式二:maven - assembly - plugin,全家桶打包

独特的打包特点

maven-assembly-plugin 主打一个 “全家桶” 概念,它生成的是所谓的 “fat jar”,也就是将项目代码以及所有依赖的 class 文件全部打包到一个 JAR 文件中。就好比你搬家时,把所有的东西,包括家具、行李等一股脑都塞进了一个超大的集装箱里,这样到了新家,只要这个集装箱在,所有东西都在,完全不用担心依赖丢失的问题。在微服务架构中,这种打包方式就非常实用,它可以确保各个服务独立运行,不用担心依赖的传递和管理。

关键的 pom.xml 配置

pom.xml 文件中,配置如下:

<build>
    <plugins>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>3.4.2</version>
            <configuration>
                <archive>
                    <manifest>
                        <mainClass>org.example.App</mainClass>
                    </manifest>
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

配置解释如下:

打包后的成果呈现

执行 mvn clean package 命令后,在 target 目录下会生成一个包含所有依赖的可执行 JAR 包,例如 java-demo-1.0-SNAPSHOT-jar-with-dependencies.jar,同时还会有一个原始的不包含依赖的 JAR 包 java-demo-1.0-SNAPSHOT.jar
可执行 JAR 包解压后,目录结构大致如下:

├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│       └── org.example
│           └── java-demo
│               ├── pom.properties
│               └── pom.xml
├── org
│   └── example
│       └── App.class
├── com
│   └── fasterxml
│       └── json2
│           └── ...  fastjson2相关类文件
├── org
│   └── slf4j
│       └── ...  slf4j相关类文件
└── ... 其他依赖的类文件

MANIFEST.MF 文件内容类似:

Manifest-Version: 1.0
Created-By: Maven Assembly Plugin 3.4.2
Build-Jdk-Spec: 17
Main-Class: org.example.App

深入权衡利弊

方式三:maven - shade - plugin,高级防冲突版

核心技术:类重定位

maven-shade-plugin 可谓是一个 “全能选手”,它不仅能像 maven-assembly-plugin 一样将项目和依赖打包成一个可执行的 “超级 JAR”(也就是 “fat jar”),还自带了一个高级技能 —— 类重定位(Relocating Classes)。在复杂的项目中,不同依赖可能会引入相同库的不同版本,或者不同依赖中有同名的类,这就好比两个搬家的人都带了同样名字的家具,放在同一个屋子里肯定会乱套,程序运行时就会报错。而 maven-shade-plugin 的类重定位功能就像是给其中一个人的家具都贴上了特殊标签,改变了它们的 “名字”(类名、包名),这样就避免了冲突。它使用 ASM 字节码操作库,在打包过程中动态地修改类的字节码,将指定的类或包移动到新的命名空间 ,从而实现不同版本依赖的共存。

详细配置步骤

pom.xml 文件中,配置如下:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.4.1</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <transformers>
                            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                <mainClass>org.example.App</mainClass>
                            </transformer>
                        </transformers>
                        <relocations>
                            <relocation>
                                <pattern>org.old.package</pattern>
                                <shadedPattern>org.new.shaded.package</shadedPattern>
                            </relocation>
                        </relocations>
                        <filters>
                            <filter>
                                <artifact>*:*</artifact>
                                <excludes>
                                    <exclude>META-INF/*.SF</exclude>
                                    <exclude>META-INF/*.DSA</exclude>
                                    <exclude>META-INF/*.RSA</exclude>
                                </excludes>
                            </filter>
                        </filters>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

配置解释如下:

实际应用案例分析

假设有一个电商项目,使用了一个老版本的支付 SDK(old-payment-sdk),它依赖 guava 18.0 版本。而项目中其他功能需要使用 guava 32.0 版本来获得新特性和性能优化。在没有使用 maven-shade-plugin 之前,项目启动时就会报错,因为不同版本的 guava 冲突了,导致一些方法找不到或者类加载错误。

使用 maven-shade-plugin 进行配置:

<configuration>
    <relocations>
        <relocation>
            <pattern>com.google.common</pattern>
            <shadedPattern>com.shaded.guava18.com.google.common</shadedPattern>
            <includes>
                <include>com.old.payment:old-payment-sdk</include>
            </includes>
        </relocation>
    </relocations>
</configuration>

配置解释:这里将 old-payment-sdk 及其依赖中的 com.google.common 包下的所有类重定位到 com.shaded.guava18.com.google.common 包下。includes 标签指定了只对 old-payment-sdk 进行重定位操作,这样项目中其他地方使用的 guava 32.0 版本就不会受到影响。

执行 mvn clean package 命令后,生成的 JAR 包中就包含了两个版本的 guava,并且不会冲突。在代码中,访问 old-payment-sdk 中的 guava 相关类时,需要使用重定位后的包名,比如原来 com.google.common.collect.Lists 现在要写成 com.shaded.guava18.com.google.common.collect.Lists 。通过这种方式,成功解决了依赖冲突问题,项目能够正常启动和运行 。

对比总结:选择最合适的打包方式

特性全面对比

为了更直观地对比这三种插件,我们将它们在打包大小、依赖处理、冲突解决、部署便捷性等方面的特性整理成如下表格:

特性maven - jar - pluginmaven - assembly - pluginmaven - shade - plugin
打包大小较小,不包含依赖较大,包含所有依赖较大,包含所有依赖
依赖处理依赖外置,在MANIFEST.MF指定路径依赖内置,全部打包进 JAR依赖内置,全部打包进 JAR
冲突解决无特殊冲突解决机制可能出现依赖类合并冲突通过类重定位解决依赖冲突
部署便捷性需保证依赖目录与 JAR 包在同一级,部署相对复杂只需一个 JAR 文件,部署便捷只需一个 JAR 文件,部署便捷
适用场景依赖管理简单,对 JAR 包大小敏感,且部署环境能保证依赖路径一致性的项目依赖管理不太复杂,追求部署便捷性,对 JAR 包大小不太敏感的项目依赖复杂,存在依赖冲突风险,需要确保不同版本依赖共存的项目

根据场景选择

针对不同项目场景,选择合适的打包方式可以事半功倍:

在实际项目中,选择合适的打包方式至关重要。希望通过本文的介绍,大家能根据项目的具体需求,灵活运用这三种打包方式,顺利完成项目的交付。如果在实践过程中有任何疑问或心得,欢迎在评论区留言分享,让我们一起进步!

结语:打包之路,从此畅通

JAR 包的打包方式各有千秋,maven-jar-plugin 以小巧轻便见长,适用于依赖管理简单的项目;maven-assembly-plugin “全家桶” 式的打包风格,让部署变得轻而易举,是追求便捷部署项目的首选;而 maven-shade-plugin 凭借强大的类重定位技术,在复杂依赖的项目中发挥着关键作用,解决了令人头疼的依赖冲突问题。

在实际的项目开发中,大家要根据项目的具体情况,如依赖的复杂程度、JAR 包大小的限制、部署环境的要求等,灵活选择合适的打包方式。希望大家通过这篇文章,对这三种主流的打包方式有了更深入的理解,在今后的 Java 开发中,能够轻松应对 JAR 包打包问题,让项目的交付更加顺利!如果在实践过程中遇到任何问题,欢迎随时在评论区留言,我们一起探讨解决 。

到此这篇关于Java可执行JAR包打包三种方式的文章就介绍到这了,更多相关Java可执行JAR包打包内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文