跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Java 可执行 JAR 包打包:三种 Maven 方案对比

Java 可执行 JAR 包打包涉及三种主流 Maven 插件方案。maven-jar-plugin 生成轻量包,依赖外置,适合简单项目;maven-assembly-plugin 生成包含所有依赖的 Fat Jar,部署便捷但体积较大;maven-shade-plugin 同样生成 Fat Jar,支持类重定位解决依赖冲突,适用于复杂依赖场景。开发者应根据项目依赖复杂度、包大小限制及部署环境选择合适的打包方式,以确保交付顺利并避免运行时错误。

活在当下发布于 2026/3/15更新于 2026/10/24108 浏览

引言: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/
          
        
      
    
    
      org.apache.maven.plugins
      maven-dependency-plugin
      3.1.1
      
        
          package
          
            copy-dependencies
          
          
            ${project.build.directory}/dependencies/
            runtime
          
        
      
    
  

</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
<executions>
<execution>
<phase>
</phase>
<goals>
<goal>
</goal>
</goals>
<configuration>
<outputDirectory>
</outputDirectory>
<includeScope>
</includeScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

配置解释如下:

  • mainClass:指定程序的入口类,也就是包含 main 方法的类。
  • addClasspath:设置为 true 表示将依赖的路径添加到 MANIFEST.MF 文件的 Class-Path 中。
  • classpathPrefix:指定依赖路径的前缀,这里表示依赖包存放在 dependencies/ 目录下。
  • maven-dependency-plugin:这个插件用于将项目的依赖复制到指定目录,outputDirectory 指定了依赖包的输出目录,includeScope 设置为 runtime 表示只复制运行时依赖的包。

打包后结构与执行命令

执行 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 指定的路径去加载依赖包。

优缺点分析

  • 优点:
    • JAR 包体积小:由于不包含第三方依赖,JAR 包本身的大小相对较小,便于传输和存储。
    • 依赖清晰:依赖包单独存放在 dependencies 目录中,依赖关系一目了然,方便管理和维护。
  • 缺点:
    • 依赖位置限制:运行时必须保证 dependencies 目录与 JAR 包在同一级目录下,否则会因为找不到依赖而运行失败,这在部署时可能会带来一些不便。例如在不同环境下部署,若目录结构发生变化,就需要手动调整依赖目录的位置。

方式二: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>

配置解释如下:

  • mainClass:指定程序的入口类,这是程序运行的起点。
  • descriptorRefs 中的 jar-with-dependencies:这是一个预定义的描述符,表示生成包含所有依赖的 JAR 包,使用这个描述符可以快速实现全家桶打包。
  • execution 部分:id 是执行的唯一标识符,phase 表示在 Maven 的 package 阶段执行该插件,goal 为 single 表示生成一个独立的聚合 JAR 包。

打包后的成果呈现

执行 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

深入权衡利弊

  • 优点:
    • 部署便捷:只需一个 JAR 文件,无需额外管理依赖包,部署时直接上传运行即可,大大简化了部署流程,降低了出错的概率。在生产环境中,运维人员可以更方便地进行部署和维护,不用担心依赖包的丢失或版本不一致的问题。
    • 依赖统一管理:所有依赖都在一个 JAR 包内,不会出现依赖冲突(只要打包时不冲突),而且在不同环境中运行时,依赖的一致性有保障,避免了因环境差异导致的依赖问题。
  • 缺点:
    • JAR 包体积大:由于包含了所有依赖,JAR 包的体积通常会比较大,这在网络传输和存储时可能会带来一些不便,比如部署到远程服务器时,上传速度会受到影响。
    • 依赖类合并冲突:在打包过程中,如果不同依赖中有同名的类,可能会导致冲突。虽然可以通过一些配置来解决部分冲突,但仍可能出现运行时错误,排查和解决这类问题往往比较困难。

方式三: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>

配置解释如下:

  • transformers 中的 ManifestResourceTransformer:用于修改 MANIFEST.MF 文件,设置 mainClass 为程序的入口类,这样生成的 JAR 包就可以直接通过 java -jar 命令运行。
  • relocations:这是类重定位的核心配置部分,pattern 指定需要重定位的原始包名,shadedPattern 指定重定位后的目标包名。比如项目中 org.old.package 下的类可能与其他依赖冲突,就可以将其重定位到 org.new.shaded.package。
  • filters:用于过滤资源,这里配置排除 META-INF 目录下的签名文件(.SF、.DSA、.RSA),因为这些文件在合并 JAR 包时可能会引起冲突。

实际应用案例分析

假设有一个电商项目,使用了一个老版本的支付 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 是一个不错的选择。它可以让 JAR 包保持较小的体积,同时依赖管理也相对简单。例如一个简单的命令行工具项目,依赖的第三方库很少,使用 maven-jar-plugin 打包后,方便在不同环境中快速部署和运行。
  • 大型项目:对于大型项目,尤其是微服务架构中的各个服务,maven-assembly-plugin 或 maven-shade-plugin 更为合适。如果依赖管理相对简单,没有复杂的依赖冲突问题,maven-assembly-plugin 的'全家桶'打包方式可以简化部署流程,提高部署效率。比如一个电商微服务项目,各个服务之间依赖明确,使用 maven-assembly-plugin 打包后,每个服务都可以独立部署,互不干扰。
  • 依赖复杂项目:当项目依赖复杂,存在不同版本依赖冲突的风险时,maven-shade-plugin 无疑是最佳选择。它通过类重定位功能,能够有效地解决依赖冲突问题,确保项目的稳定运行。例如一个大型的企业级应用,集成了多个不同团队开发的模块,每个模块可能依赖不同版本的相同库,使用 maven-shade-plugin 可以让这些依赖和谐共处,避免因冲突导致的运行时错误。

在实际项目中,选择合适的打包方式至关重要。希望通过本文的介绍,大家能根据项目的具体需求,灵活运用这三种打包方式,顺利完成项目的交付。

目录

  1. 引言:JAR 包打包的困扰与重要性
  2. 方式一:maven-jar-plugin,轻量但依赖外置
  3. 原理剖析
  4. pom.xml 配置详解
  5. 打包后结构与执行命令
  6. 优缺点分析
  7. 方式二:maven-assembly-plugin,全家桶打包
  8. 独特的打包特点
  9. 关键的 pom.xml 配置
  10. 打包后的成果呈现
  11. 深入权衡利弊
  12. 方式三:maven-shade-plugin,高级防冲突版
  13. 核心技术:类重定位
  14. 详细配置步骤
  15. 实际应用案例分析
  16. 对比总结:选择最合适的打包方式
  17. 特性全面对比
  18. 根据场景选择

更多推荐文章

查看全部
  • 十大 AI 副业赛道:普通人如何利用 AI 工具实现额外收入
  • 图数据库 Neo4j 和 JDK 安装与配置教程
  • Linux 基础操作与 Java 项目云端部署实战
  • Windows 本地运行 DeepSeek 开源模型的三个步骤
  • SpringBoot 集成 Stable-Diffusion-3.5 微服务实践
  • C++ 搜索引擎通用工具:文件读取与中文分词实现
  • 语义化 AI 驱动器:提示词工程的技术演进与治理
  • Rust 与 WebAssembly 实战:在浏览器与 Node.js 中运行高性能代码
  • 本地免费部署 Llama 3.1 大模型详细教程
  • 基于 PSO 与 DWA 融合的无人机三维动态避障路径规划研究
  • 网络安全入门指南:成为安全工程师的五项核心能力
  • Java Stream filter 多条件组合技巧
  • 基于 Isaac Lab 的 Robot Lab 机器人强化学习使用指南
  • Mac 系统安装与配置 Claude Code 命令行工具
  • 网络安全技术核心学习内容及就业前景分析
  • 告别模板爆炸:构建可读性强的 C++ 元程序的 7 种模式
  • Comet 浏览器(Perplexity AI 推出)功能解析与使用指南
  • OSCP 实战:Net-NTLMv2 哈希传递与中继攻击详解
  • System V 进程通信详解:消息队列与信号量
  • Python 数据分析实战:从数据导入到统计输出

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online