Maven uber-jar(带依赖的打包插件)maven-shade-plugin
文章目录
- 最基础的 maven-shade-plugin 使用
- 生成可执行的 Jar 包 和 常用的资源转换类
- 包名重命名
- 打包时排除依赖
- 与其他常用打包插件比较
本文是对 maven-shade-plugin 常用配置的介绍,更详细的学习请参照 Apache Maven Shade Plugin 官方文档
通过使用 maven-shade-plugin 插件进行 Maven 的打包操作,可以将项目中的依赖一同添加到最终的项目 Jar 包内,maven-shade-plugin 插件有两个目标,我们要学习的是插件的 shade 目标,建议使用时与 Maven 生命周期的 package 阶段绑定
这中打包后带依赖的 Jar 包一般称为 uper-jar 或 fat-jar
不管 pom.xml 是否声明了 Maven 的默认打包插件 maven-jar-plugin,也不管是否声明了其他打包插件,maven-jar-plugin 都会在 package 阶段最先执行,而 maven-shade-plugin 插件的 shade 目标,正是对 maven-jar-plugin 打包后的 Jar 包进行二次打包,同时将项目依赖的添加进去
最基础的 maven-shade-plugin 使用
按照如下配置 15~29 行,就可以正确的将 maven-shade-plugin 插件的 shade 目标与 Maven 的生命周期 package 阶段绑定,之后我们就可以通过 Maven 生命周期命令 mvn package 执行插件完成打包操作:
<!-- 模拟项目中使用的依赖 -->
<dependencies>
<!-- 依赖 commons-lang3 -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version>
</dependency>
</dependencies><!-- 对项目构建进行配置 -->
<build><plugins><!-- 引入 maven-shade-plugin 插件 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.2.1</version><executions><execution><!-- 绑定 Maven 生命周期的 package 阶段 --><phase>package</phase><goals><!-- package 阶段执行时,让其调用插件的 repackage 目标 --><goal>shade</goal></goals></execution></executions></plugin></plugins>
</build>
打包后,会将打包文件放到 ${project.build.directory} 中(默认是 target 目录),如下图:

上图中,我们重点关注两个文件:
-
ares5k-package-1.0-SNAPSHOT.jar:
maven-shade-plugin对maven-jar-plugin生成的 Jar 包进行二次打包后的 Jar 包,这个 Jar 包内已经包函项目的依赖了 -
original-ares5k-package-1.0-SNAPSHOT.jar:原始 Jar 包,
maven-jar-plugin生成的不包含项目依赖的 Jar 包,maven-shade-plugin为了避免原始 Jar 包和新 Jar 包名字冲突,对原始 Jar 包进行了重命名,添加了original-前缀
将 maven-shade-plugin 生成的 Jar 包解压,观察其内部结构可以发现,maven-shade-plugin 打包后并没有将项目中依赖的 Jar 包直接存放,而是将依赖的 Jar 包解压后存放的:
这个特性很重要,因为 maven-shade-plugin 的另一个重要功能 <重命名> 就是基于这个实现的

另外,通过上面配置创建的 Jar 包是不能够直接运行的,因为我们没有指定项目的入口类,所以只适合让其他项目引用
生成可执行的 Jar 包 和 常用的资源转换类
仿照下面 19~27 行,就可以生成可直接运行的 Jar 包:
<!-- 对项目构建进行配置 -->
<build><plugins><!-- 引入 maven-shade-plugin 插件 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.2.1</version><executions><execution><!-- 绑定 Maven 生命周期的 package 阶段 --><phase>package</phase><goals><!-- package 阶段执行时,让其调用插件的 repackage 目标 --><goal>shade</goal></goals></execution></executions><!-- 设置插件属性 --><configuration><transformers><!-- 添加入口类 --><transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><!-- 入口类 --><mainClass>com.ares5k.BB</mainClass></transformer></transformers></configuration></plugin></plugins>
</build>
上面代码中 ManifestResourceTransformer 是 maven-shade-plugin 插件提供的资源转换类,就是用来指定入口类的,除此之外,还有两个常用的资源转换类,AppendingTransformer 和 ServicesResourceTransformer
AppendingTransformer
这个资源转换类的作用,是在打包时,当碰见项目中或依赖中有同名的资源文件,要对文件内容进行追加操作,而不是覆盖
最常见的场景就是项目中引入大量 Spring 依赖时,每个 Spring 依赖都有自己的 META-INF/spring.handlers、META-INF/spring.schemas 和 META-INF/spring.factories 等文件,这种情况下利用 maven-shade-plugin 插件打包,就会发生文件覆盖的问题,如果被覆盖的文件内容和新文件的内容一样,可能会掩盖这个现象,但是当内容不一样时,就会导致项目运行错误
以 Spring 为例,演示同名文件追加操作的配置 16~27 行:
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.2.1</version><executions><execution><!-- 绑定 Maven 生命周期的 package 阶段 --><phase>package</phase><goals><!-- package 阶段执行时,让其调用插件的 repackage 目标 --><goal>shade</goal></goals></execution></executions><!-- 设置插件属性 --><configuration><transformers><!-- 设置 META-INF/spring.handlers 文件追加而非覆盖 --><transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"><resource>META-INF/spring.handlers</resource></transformer><!-- 设置 META-INF/spring.schemas 文件追加而非覆盖 --><transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"><resource>META-INF/spring.schemas</resource></transformer><!-- 设置 META-INF/spring.factories文件追加而非覆盖 --><transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"><resource>META-INF/spring.factories</resource></transformer></transformers></configuration>
</plugin>
ServicesResourceTransformer
这个资源转换类的作用,是在打包时,当碰见同名的 SPI 接口文件时,会将文件内容追加而不是覆盖
我们使用 Java SPI 机制时,会在 META-INF/services 中创建与接口同名的文件来记录具体的实现类,当有多个同名文件时,说明该接口有多个实现类,这时候就可以利用 ServicesResourceTransformer 将文件内容合并,而不是覆盖 16~21 行:
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.2.1</version><executions><execution><!-- 绑定 Maven 生命周期的 package 阶段 --><phase>package</phase><goals><!-- package 阶段执行时,让其调用插件的 repackage 目标 --><goal>shade</goal></goals></execution></executions><!-- 设置插件属性 --><configuration><transformers><!-- 设置 Java SPI 追加而非覆盖 --><transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/></transformers></configuration>
</plugin>
包名重命名
重命名功能也是 maven-shade-plugin 插件的一个重要功能,这个功能可以对项目中或依赖中的包名进行重命名,包名重命名后还会把引用的地方一并修改,非常方便
这个功能的使用场景,主要是版本冲突,举个例子吧,前置条件如下:
-
我们的项目 A 中引用了
commons-lang3,另一个需要依赖我们项目的工程 B 也引入了commons-lang3 -
两个
commons-lang3的版本不一样,拥有相同包名、类名,但功能不同的类
基于上述条件,两个 commons-lang3 中相同包名类名的类只会被 JVM 加载一个,这就可能导致项目运行时出问题,比如调用的是未被加载的类中的功能,这时候就可以使用包名重命名功能防止 JVM 只加载一个类的情况
下面配置中 17~27 行就是对包名重新命名:
<!-- 引入 maven-shade-plugin 插件 -->
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.2.1</version><executions><execution><!-- 绑定 Maven 生命周期的 package 阶段 --><phase>package</phase><goals><!-- package 阶段执行时,让其调用插件的 repackage 目标 --><goal>shade</goal></goals></execution></executions><!-- 设置插件属性 --><configuration><relocations><!-- 包名重命名 --><relocation><!-- 旧包名 --><pattern>org.apache.commons.lang3</pattern><!-- 新包名 --><shadedPattern>shade.org.apache.commons.lang3</shadedPattern></relocation></relocations></configuration>
</plugin>
重命名后,项目中引用的地方也会自动修改,我们通过反编译看一下重命名后使用原包名的 .class 文件,引用的包名已经自动替换成新的名字:

打包时排除依赖
排除整个依赖
打包时,想排除不需要的依赖,可以使用下面配置 17~24 行,这种方式是将整个依赖排除掉,语法为 groupId:artifactId:
<!-- 引入 maven-shade-plugin 插件 -->
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.2.1</version><executions><execution><!-- 绑定 Maven 生命周期的 package 阶段 --><phase>package</phase><goals><!-- package 阶段执行时,让其调用插件的 repackage 目标 --><goal>shade</goal></goals></execution></executions><!-- 设置插件属性 --><configuration><artifactSet><!-- 排除依赖,groupId:artifactId --><excludes><exclude>org.apache.commons:commons-lang3</exclude></excludes></artifactSet></configuration>
</plugin>
排除依赖中的部分文件
也可以更详细的配置,排除依赖中的某些文件,而不是整个依赖排除,17~30 行 :
<!-- 引入 maven-shade-plugin 插件 -->
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.2.1</version><executions><execution><!-- 绑定 Maven 生命周期的 package 阶段 --><phase>package</phase><goals><!-- package 阶段执行时,让其调用插件的 repackage 目标 --><goal>shade</goal></goals></execution></executions><!-- 设置插件属性 --><configuration><filters><!-- 过滤 commons-lang3 依赖中的内容--><filter><artifact>org.apache.commons:commons-lang3</artifact><excludes><!-- 排除 commons-lang3 依赖中 org/apache/commons/lang3/math 下的所有文件--><exclude>org/apache/commons/lang3/math/**</exclude><!-- 排除 commons-lang3 依赖中 META-INF 下所有扩展名是 .txt 的文件--><exclude>META-INF/*.txt</exclude></excludes></filter></filters></configuration>
</plugin>
排除项目中未使用的依赖
将项目中,引用后,实际没有使用的依赖排除掉,这个功能可以帮我们排除多余的包,来减小最后生成的 Jar 包的大小,
用的时候要注意一点,因为插件判断依赖是否使用,是通过依赖中的类是否被项目中的类引用来作为依据,假设我们使用 XML 版的 Spring,在 <bean> 中配置了依赖中的类,这个时候插件是无法检测到的,所以仍会将其排除
<!-- 引入 maven-shade-plugin 插件 -->
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.2.1</version><executions><execution><!-- 绑定 Maven 生命周期的 package 阶段 --><phase>package</phase><goals><!-- package 阶段执行时,让其调用插件的 repackage 目标 --><goal>shade</goal></goals></execution></executions><!-- 设置插件属性 --><configuration><!-- 将项目中未使用的依赖排除 --><minimizeJar>true</minimizeJar></configuration>
</plugin>
与其他常用打包插件比较
除了 maven-shade-plugin 之外,常用的打包插件还有 maven-jar-plugin 和 spring-boot-maven-plugin,使用方法在我其他文章中也有记录,在此将它们作一个简单的比较
maven-shade-plugin
maven-shade-plugin 也可以将项目的依赖打进最终的项目 Jar 包中,但是其与 spring-boot-maven-plugin 不同的是,spring-boot-maven-plugin 是直接将依赖的 Jar 包放进项目的 Jar 包中,而 maven-shade-plugin 则是将依赖的 Jar 包解压,然后将解压后的文件放进最终的项目 Jar 包中
maven-shade-plugin 将依赖的 Jar 包解压后添加到项目的 Jar 包中的做法,为 maven-shade-plugin 带来了另一个重要的功能 <重命名>,因为将依赖的 Jar 包解压后都是以文件形式存在,所以 maven-shade-plugin 支持对对依赖的某个具体文件进行重命名,maven-shade-plugin 在重命名时,不只是将文件名字修改,连我们项目中对其引用的地方都会一同修改
重命名的做法可以避免版本冲突,想详细了解的可以参考我 maven-shade-plugin 的文章
spring-boot-maven-plugin
spring-boot-maven-plugin 是 Spring 提供的一个 Maven 打包插件,可以通过 maven 的插件命令运行,但是一般习惯将它与 maven 生命周期绑定,然后通过 maven 生命周期命令运行,它的特点是可以将项目中依赖的 Jar 包添加到最终生成的项目 Jar 包中
spring-boot-maven-plugin 主要是对 maven-jar-plugin 生成的项目 Jar 包进行二次打包,并将项目依赖的 Jar 包添加进项目的 Jar 包中
maven-jar-plugin
maven 生命周期中 package 阶段的默认插件,不管是否在 pom.xml 中主动声明,也不管是否有其他的 package 阶段插件被绑定,其在 package 阶段都会被最先执行
使用 maven-jar-plugin 打包时,不会将依赖的 Jar 包添加到生成的项目 Jar 包中,所以当项目中使用依赖时,需要自己准备依赖的 Jar 包,这样 maven-jar-plugin 打出的项目 Jar 包才能被成功运行
相关文章:
Maven uber-jar(带依赖的打包插件)maven-shade-plugin
文章目录 最基础的 maven-shade-plugin 使用生成可执行的 Jar 包 和 常用的资源转换类包名重命名打包时排除依赖与其他常用打包插件比较 本文是对 maven-shade-plugin 常用配置的介绍,更详细的学习请参照 Apache Maven Shade Plugin 官方文档 通过使用 maven-shade…...
MySQL基础(二十八)索引优化与查询优化
都有哪些维度可以进行数据库调优?简言之: 索引失效、没有充分利用到索引——索引建立关联查询太多JOIN (设计缺陷或不得已的需求)——SQL优化服务器调优及各个参数设置(缓冲、线程数等)———调整my.cnf。数据过多――分库分表 关于数据库调优的知识点非常分散。不同的DBMS&…...
初步认识性能测试和完成一次完整的性能测试
上一篇博文主要通过两个例子让测试新手了解一下测试思想,和在做测试之前应该了解人几点,那么我们在如何完成一次完整的性能测试呢? 测试报告是一次完整性能测试的体现,所以,这里我给出一个完整的性能测试报告ÿ…...
使用插件快速生成代码
使用插件快速生成代码 咋们常说,授人以鱼不如授人以渔,在这里给大家提供一些技巧性的东西,方便一些新手同学可以快速上手,同时,也提高我们的开发兴趣与开发热情! 主要讲什么呢,我们来学一学如何…...
FE_Vue学习笔记 插槽 slot
插槽分为匿名插槽、具名插槽、作用域插槽。子组件中: 匿名插槽只能有一个;可以有多个具名插槽;作用域插槽中可以有匿名插槽和具名插槽。 当项目中一个组件可以多次复用时,我们可以把这个组件封装成单独的.vue文件,从…...
单链表的成环问题
前言:链表成环问题不仅考察双指针的用法,该问题还需要一定的数学推理和分析能力,看似简单的题目实则细思缜密,值得斟酌~ 目录 1.问题背景引入-判断链表是否成环: 1.1.正解:快慢指针 1.2 STL的集合判重 …...
横截面收益率
横截面收益率指的是在经典资产定价模型中,在横截面上线性确定的一个与资产风险匹配的资产收益率。 横截面收益率的预测[1] (一)变量和方法 我们主要使用月度频率数据进行检验。交易数据和公司财务数据来自于CSMAR数据库。CSMAR数据库的收益率调整了送股、配股以及拆…...
C++解析JSON JSONCPP库的使用
首先去GitHub下载JSONCPP的源码: JSonCpp的源码 解压后得到:jsoncpp-master 文件夹 需要的是:jsoncpp-master\src\lib_json 目录下的所有文件和 jsoncpp-master\include\json 目录下的所有文件,在MFC工程目录下新建两个文件夹或…...
不会Elasticsearch标准查询语句,如何分析数仓数据?
1 Elasticsearch的查询语句 ES中提供了一种强大的检索数据方式,这种检索方式称之为Query DSL,Query DSL是利用Rest API传递JSON格式的请求体(Request Body)数据与ES进行交互,这种方式的丰富查询语法让ES检索变得更强大,更简洁。 1.1 查询预发 # GET /…...
获得GitHub Copilot并结合VS Code使用
一、什么是GitHub Copilot GitHub Copilot是一种基于AI的代码生成工具。它使用OpenAI的GPT(生成式预训练Transformer)技术来提供建议。它可以根据您正在编写的代码上下文建议代码片段甚至整个函数。 要使用GitHub Copilot,您需要在编辑器中…...
Java基础-判断和循环
1 流程控制语句 在一个程序执行的过程中,各条语句的执行顺序对程序的结果是有直接影响的。所以,我们必须清楚每条语句的执行流程。而且,很多时候要通过控制语句的执行顺序来实现我们想要的功能。 1.1 流程控制语句分类 顺序结构 判…...
ESP32 FreeRTOS学习总结
2023.5.11 FreeRTOS中文数据手册:https://www.freertos.org/zh-cn-cmn-s/RTOS.html 感谢以下两位B站UP主的教程:孤独的二进制、Michael_ee 1.Task 创建任务常用API: 任务函数描述xTaskCreate()使用动态的方法创建一个任务xTaskCreatePinne…...
uniapp打包ios保姆式教程【最新】
uniapp打包 打包方式ios打包一、前往官网登录二、添加证书 三、添加标识符(Identifiers)四、添加安装ios测试机(Devices)五、获取证书profile文件六、生成并下载p12文件七、开始打包 打包方式 安卓打包直接使用公共测试证书即可打包成功,简单方便,这里我…...
Thread线程学习(2) Linux线程的创建、终止和回收
目录 1.首先要了解什么是线程ID,以及它的作用是什么 2.创建线程 3.终止线程 4.回收线程 5.总结 在Linux系统中,线程是轻量级的执行单元,能够在同一个进程中并发执行。本文将介绍如何在Linux环境下创建、终止和回收线程,并提供…...
linux-项目部署软件安装
安装jdk 操作步骤: 1、使用FinalShell自带的上传工具将jdk的二进制发布包上传到Linux jdk-8u171-linux-x64.tar.gz 2、解压安装包,命令为tar -zxvf jdk-8u171-linux-x64.tar.gz -C /usr/local 3、配置环境变量,使用vim命令修改/etc/profile文…...
Vue3-黑马(三)
目录: (1)vue3-基础-计算属性 (2) vue3-基础-xhr-基本使用 (3)vue3-基础-xhr-promise改造 (1)vue3-基础-计算属性 上面有重复的代码,用计算属性࿰…...
标准C库函数fprintf(),sprintf(),snprintf()的函数使用方法(往文件中写入数据,将变量的值转换成字符串输出)
前言 如果,想要深入的学习标准C库中函数fprintf(),sprintf(),snprintf(),还是需要去自己阅读Linux系统中的帮助文档。 具体输入命令: man 3 fprintf/sprintf/snprintf即可查阅到完整的资料信息。 fprintf 函数 fprin…...
不到1分钟,帮你剪完旅行vlog,火山引擎全新 AI「神器」真的这么绝?
旅行时,想在社交平台发布一支精美的旅行 vlog,拍摄剪辑需要花费多长时间? 20 分钟?一小时?半天? 在火山引擎算法工程师眼里,可能 1 分钟都用不了,因为会有 AI 替你完成。 没错&#…...
MySQL的概念、编译安装,以及自动补全
一.数据库的基本概念 1、数据(Data) • 描述事物的符号记录 • 包括数字,文字,图形,图像,声音,档案记录等 • 以“记录”形式按统一的格式进行存储 2、表 • 将不同的记录组织在一起 • …...
Jmeter常见问题和工作中遇到的问题解决方法汇总
一、标题Jmeter常见问题解决 1.1 Jmeter如何针对https协议进行接口测试? 解决方法: 协议更改为:https,端口号更改为443;Jmeter默认的是:http协议,端口号是:80 1.2 Jmeter如何解决默…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...
