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如何解决默…...

蓝牙单连接和多连接知识
蓝牙单连接和多连接知识 生活中蓝牙连接多路蓝牙的情况越来越多,下面介绍下单连接和多连接的知识,供大家参考 一:蓝牙单连接 蓝牙单连接也称为Point-to-Point 点对点连接,是最常见的蓝牙连接场景,如手机<–>车载蓝牙。&…...

Mongodb—查询数据报错 Sort operation used more than the maximum 33554432 bytes of RAM
线上服务的MongoDB中有一个很大的表数据有十几万条。然后报了下面这个错误: “exception”:”org.springframework.data.mongodb.UncategorizedMongoDbException”, “message”:”Query failed with error code 96 and error message ‘Executor error during find command:…...

Java内存异常和垃圾回收机制
内存溢出异常 Java会存在内存泄漏吗?请简单描述 内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说, Java是有GC垃圾回收机制 的,也就是说,不再被使用的对象,会被GC自动回收 掉,自动…...

linux系统挂载逻辑卷和扩展逻辑卷组
fdisk /dev/vdb fdisk /dev/vdc在分区后需要修改分区类型为 8e,操作过程类似: [rootlocal ~] $ fdisk /dev/xvdb #### 选择磁盘 Command (m for help): m #### 帮助 Command actiona tog…...

WPF:WPF原生布局说明
前言 WPF在国内讨论度很小,我在这里记录一下WPF简单的原生控件是如何使用的,顺便回忆一下WPF的基础知识,有些忘记的比较厉害了 WPF简介 WPF是微软推出的桌面UI软件,是我觉得最早实现MVVM(数据驱动事务)&…...

SpringMVC常用注解用法
Spring MVC是基于Servlet API构建的原始Web框架。 MVC是Model View Controller的缩写即视图模型控制器,是一种思想,而Spring MVC是对该思想的具体实现。关于SpringMVC的学习我们需要掌握用户和程序的连接、获取参数以及返回数据三大部分。而这三大功能的…...

Liunx find locate 命令详解
文章目录 find补充说明语法选项参数实例根据文件或者正则表达式进行匹配否定参数根据文件类型进行搜索基于目录深度搜索根据文件时间戳进行搜索根据文件大小进行匹配删除匹配文件根据文件权限/所有权进行匹配借助-exec选项与其他命令结合使用搜索但跳过指定的目录find其他技巧收…...

JAVA并发专题(1)之操作系统底层工作的整体认识
一、分诺依曼计算机模型 现代计算机模型是基于-冯诺依曼计算机模型,计算机在运行时,先从内存中取出第一条指令,通过控制器的译码,按指令的要求,从存储器中取出数据进行指定的运算和逻辑操作等加工,然后再按…...

WiFi(Wireless Fidelity)基础(七)
目录 一、基本介绍(Introduction) 二、进化发展(Evolution) 三、PHY帧((PHY Frame ) 四、MAC帧(MAC Frame ) 五、协议(Protocol) 六、安全&#x…...

Agilent安捷伦33522B任意波形发生器
Agilent安捷伦33522B任意波形发生器30兆赫 2通道 为您最苛刻的测量生成全方位信号的无与伦比的能力 具有 5 倍低谐波失真的正弦波,可提供更纯净的信号 脉冲频率高达 30 MHz,抖动减少 10 倍,可实现更精确的计时 具有排序功能的逐点任意波形功能…...