maven 插件之 maven-shade-plugin,解决同包同名 class 共存问题的神器
开心一刻
有一天螃蟹出门,不小心撞倒了泥鳅
泥鳅很生气地说:你是不是瞎啊!
螃蟹说:不是啊,我是螃蟹
概述
maven-shade-plugin 官网已经介绍的很详细了,我给大家简单翻译一下
This plugin provides the capability to package the artifact in an uber-jar, including its dependencies and to shade - i.e. rename - the packages of some of the dependencies.
这段话简明扼要的概述了 maven-shade-plugin 的功能
能够将项目连同其依赖,一并打包到一个
uber-jar
中uber-jar 就是一个超级 jar,不仅包含我们的工程代码,还包括依赖的 jar,和
spring-boot-maven-plugin
类似能够对依赖 jar 中的包名进行重命名
这个功能就有意思了,后面我们详说
maven-shade-plugin 必须和 Maven 构建生命周期的 package 阶段绑定,那么当 Maven 执行 mvn package
时会自动触发 maven-shade-plugin;使用很简单,在 pom.xml
添加该插件依赖即可
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<!-- 和 maven package 阶段绑定 -->
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<!-- 按需自定义配置 -->
</configuration>
</execution>
</executions>
</plugin>
phase
和 goal
按如上固定配置,configuration
才是我们自由发挥的平台;有了基本了解后,我们再结合官方提供的 Examples
来看看 maven-shade-plugin 具体能干啥
选择打包内容
假设我们有项目 maven-shade-plugin-demo
,其项目结构如下
如果不做任何剔除,可以按如下配置进行全打包
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<!-- 和 package 阶段绑定 -->
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<!-- 按需自定义配置 -->
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
执行 mvn package
后,我们会看到两个包
maven-shade-plugin-demo-1.0-SNAPSHOT.jar
就是 uber-jar;解压可看其结构
不仅包括 package
、还包括各种配置文件、元文件,统统打包进 uber-jar;而 original-maven-shade-plugin-demo-1.0-SNAPSHOT.jar
则是不包括依赖 jar 的原始项目包;如果我们比较细心的话,会发现打包的时候告警了
意思是说 hutool jar 包中有 META-INF/MANIFEST.MF
,而 maven-shade-plugin-demo
打包成 jar 后也包含 META-INF/MANIFEST.MF
,两者重复了,只会将其中一个复制进 uber jar;默认情况下,是将我们项目的 jar 中的 META-INF/MANIFEST.MF
复制进 uber jar
那如果我们想保留 hutool 下的 MANIFEST.MF,而去掉 maven-shade-plugin-demo 中的 MANIFEST.MF,该如何处理呢?只需要微调下 configuration
<configuration>
<filters>
<filter>
<artifact>com.qsl:maven-shade-plugin-demo</artifact>
<excludes>
<exclude>META-INF/*.MF</exclude>
</excludes>
</filter>
</filters>
</configuration>
此时 uber jar 中的 MANIFEST.MF 就来自 hutool jar 了
回到前面的 configuration
配置,我们需要明白其每个子标签的含义
filter
:过滤器,可以配置多个artifact
:复合标识符,用来匹配 jar,简单点说,就是匹配 jar 的匹配规则
按 Maven 的坐标:groupId:artifactId[[:type]:classifier] 进行配置,
groupId:artifactId
必配,[[:type]:classifier]
选配;支持通配符*
和?
,例如:<artifact>*:*</artifact>
(相当于匹配上所有jar)exclude
:排除项,也就是不会复制进 uber-jar;支持通配符配置include
:包含项,也就是只有这些会被复制进 uber-jar;支持通配符配置
我们实战下,假设我们项目结构如下所示
configuration
配置如下
<configuration>
<filters>
<filter>
<artifact>com.qsl:maven-shade-plugin-demo</artifact>
<excludes>
<exclude>com/qsl/test/**</exclude>
<exclude>com/qsl/Entry.class</exclude>
</excludes>
</filter>
<filter>
<artifact>cn.hutool:hutool-all</artifact>
<includes>
<include>cn/hutool/Hutool.class</include>
<include>cn/hutool/json/**</include>
</includes>
</filter>
</filters>
</configuration>
执行 mvn package
后,uber-jar 内部结构你们能想到吗?我们来看看实际结果
是不是和跟你们想的一样?
除了手动指定 filter
外,此插件还支持自动移除项目中没有使用到的依赖类,以此来最小化 uber jar 的体积;configuration 配置如下
<configuration>
<minimizeJar>true</minimizeJar>
</configuration>
我们在 StringUtil
中引入 hutool 的 StrUtil(相当于项目依赖了 StrUtil)
package com.qsl.util;
import cn.hutool.core.util.StrUtil;
/**
* @author: 青石路
*/
public class StringUtil {
public static boolean isBlank(String str) {
return StrUtil.isBlank(str);
}
}
然后打包,uber-jar 内部结构如下所示
从 maven-shade-plugin 1.6 开始,minimizeJar
会保留 filter
中 include
配置的类,但是要注意:
inlcude 默认会排除所有不在 include 配置中的类
这就会导致问题,我们来看个案例,我们引入 logback
依赖,但代码中未用到它,而我们又想将其下的 class 复制进 uber-jar,另外我们还想将 hutool 的 cn/hutool/json
包下的全部类都复制进 uber-jar,并且开启 minimizeJar
,是不是按如下配置?
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.3.14</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<!-- 和 package 阶段绑定 -->
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<filters>
<filter>
<artifact>ch.qos.logback:logback-classic</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<artifact>cn.hutool:hutool-all</artifact>
<includes>
<include>cn/hutool/json/**</include>
</includes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
打包后看 uber-jar 目录结构
hutool 的 core
包没有复制进来,这是因为我们对 hutool 配置了 include
,默认把最小依赖的 core
包给排除掉了,那怎么办呢?插件提供了配置 <excludeDefaults>false</excludeDefaults>
来处理此种情况,它会覆盖 include
默认排除行为
<filter>
<artifact>cn.hutool:hutool-all</artifact>
<excludeDefaults>false</excludeDefaults>
<includes>
<include>cn/hutool/json/**</include>
</includes>
</filter>
这样配置之后,既能包含 hutool 的 json 包,又能包含最小依赖的 core 包
false 通常配合 true 来使用,不然
<configuration>
<filters>
<filter>
<artifact>cn.hutool:hutool-all</artifact>
<excludeDefaults>false</excludeDefaults>
<includes>
<include>cn/hutool/json/**</include>
</includes>
</filter>
</filters>
</configuration>
这么配置有何意义?
重定位 class
如果 uber-jar 被其他项目依赖,而我们的 uber-jar 又是保留了依赖 jar 的 class 的全类名,那么就可能类重复而导致类加载冲突;比如项目A依赖了我们的 maven-shade-plugin-demo
,还依赖了 B.jar,两个 jar 中都存在 cn.hutool.core.util.StrUtil.class
,但 api 完全不一样,根据 双亲委派模型
,只会成功加载其中某个 cn.hutool.core.util.StrUtil.class
,那么另一个的 api 则使用不了。为了解决这个问题,插件提供了重定位功能,通过创建 class 字节码的私有副本,按新配置的 package,打包进 uber-jar
我们来看个案例,假设我们只需要 hutool 的 core 包,将其下所有的 class 按 com.qsl.core
包打包进 uber-jar,可以按如下配置
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<!-- 和 package 阶段绑定 -->
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>cn.hutool.core</pattern>
<shadedPattern>com.qsl.core</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>cn.hutool:hutool-all</artifact>
<includes>
<include>cn/hutool/core/**</include>
</includes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
打包后 uber-jar 目录结构如下
我们来看下 uber-jar 中的 StringUtil.class
依赖的 StrUtil
也被正确调整了,是不是很牛皮?
此时项目A 依赖 B.jar 的同时,又依赖我们的 maven-shade-plugin-demo
,就不会类重名了(package 不一致了)
relocation
同样支持 exclude
和 include
<configuration>
<relocations>
<relocation>
<pattern>cn.hutool.core</pattern>
<shadedPattern>com.qsl.core</shadedPattern>
<!-- exclude 指定的不重定向,其他重定向 -->
<excludes>
<exclude>cn.hutool.core.util.ObjUtil</exclude>
<!-- 一个*只会过滤包下的class,两个*会过滤包下的class和子包 -->
<exclude>cn.hutool.core.date.**</exclude>
</excludes>
</relocation>
<relocation>
<pattern>cn.hutool.json</pattern>
<shadedPattern>com.qsl.json</shadedPattern>
<!-- include 指定的重定向,其他不重定向 -->
<includes>
<include>cn.hutool.json.JSONUtil</include>
<!-- 一个*只会过滤包下的class,两个*会过滤包下的class和子包 -->
<include>cn.hutool.json.xml.**</include>
</includes>
</relocation>
</relocations>
<filters>
<filter>
<artifact>cn.hutool:hutool-all</artifact>
<includes>
<include>cn/hutool/core/**</include>
<include>cn/hutool/json/**</include>
</includes>
</filter>
</filters>
</configuration>
此时 uber-jar 的目录结构是怎样的?你们自己去试!
生成附属包
前面已经介绍过,打包后会生成两个包
但 original
开头的那个明显不是按 Maven 坐标命名的,所以它是不能够 install
到本地或者远程仓库的;如果需要将两个 jar 都 install
到仓库中,那么就需要用到插件的 Attaching the Shaded Artifact
(生成附属包)功能
<configuration>
<relocations>
<relocation>
<pattern>cn.hutool.core</pattern>
<shadedPattern>com.qsl.core</shadedPattern>
<!-- exclude 指定的不重定向,其他重定向 -->
<excludes>
<exclude>cn.hutool.core.util.ObjUtil</exclude>
<!-- 一个*只会过滤包下的class,两个*会过滤包下的class和子包 -->
<exclude>cn.hutool.core.date.**</exclude>
</excludes>
</relocation>
<relocation>
<pattern>cn.hutool.json</pattern>
<shadedPattern>com.qsl.json</shadedPattern>
<!-- include 指定的重定向,其他不重定向 -->
<includes>
<include>cn.hutool.json.JSONUtil</include>
<!-- 一个*只会过滤包下的class,两个*会过滤包下的class和子包 -->
<include>cn.hutool.json.xml.**</include>
</includes>
</relocation>
</relocations>
<filters>
<filter>
<artifact>cn.hutool:hutool-all</artifact>
<includes>
<include>cn/hutool/core/**</include>
<include>cn/hutool/json/**</include>
</includes>
</filter>
</filters>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>qsl</shadedClassifierName>
</configuration>
部署到仓库的 jar 如下
可执行 JAR
这个就比较简单了,我们直接看配置
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.qsl.Entry</mainClass>
</transformer>
</transformers>
</configuration>
如上配置会将 Main-Class
写进 uber-jar 的 MANIFEST.MF,还可以通过 manifestEntries
自定义属性
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<mainClass>com.qsl.Entry</mainClass>
<Build-Author>qsl</Build-Author>
</manifestEntries>
</transformer>
</transformers>
</configuration>
打包之后,uber-jar 的 MANIFEST.MF 内容如下
资源转换器
Resource Transformers 已经介绍的很详细了,我就不一一介绍了,挑几个个人认为比较重要的简单讲一下
ServicesResourceTransformer
合并 META-INF/services/
下的文件,并对文件中的 class 进行重定向;我们来看个例子,hutool 下有文件 cn.hutool.aop.proxy.ProxyFactory
我们也自定义一个
configuration 配置如下
<configuration>
<relocations>
<relocation>
<pattern>cn.hutool.aop</pattern>
<shadedPattern>com.qsl.aop</shadedPattern>
</relocation>
</relocations>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
打包后,hutool 与 uber-jar 的 cn.hutool.aop.proxy.ProxyFactory 文件内容差异如下
如果不配置 ServicesResourceTransformer
,结果是怎样,你们自己去试
AppendingTransformer
将多个同名文件的内容合并追加到一起(不配置的情况下会覆盖,最终文件内容只是其中某个文件的内容),configuration 配置如下
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.factories</resource>
</transformer>
</transformers>
</configuration>
打包后文件内容合并如下
XmlAppendingTransformer
、ResourceBundleAppendingTransformer
功能类似,只是针对的文件内容格式略微有点特殊,就不演示了,你们自行去测试
同包同名 class 共存
回到我们的主题,如果我们项目依赖的 jar 中出现了同名的 class (包名和类名均相同),根据 双亲委派模型
,只会加载其中某一个 class,虽然两个 class 同名了,但功能完全不一样,另一个未被加载的 class 的功能则用不了,如果想同时使用这两个同名 class 的功能,我们该如何处理?
文中给出了几种解决方案(注意看评论区),最高效最实用的当属 maven-shade-plugin
;假设我们项目依赖的 A.jar 和 B.jar 都存在 com.qsl.Hello.class
,我们可以新建一个项目,名字叫 qsl-a
,没有任何代码,仅仅依赖 A.jar,然后利用 maven-shade-plugin 的 Relocating Classes
功能对 A.jar 中存在重名的 class 进行重定向,例如
<configuration>
<relocations>
<relocation>
<pattern>com.qsl</pattern>
<shadedPattern>com.qsla</shadedPattern>
</relocation>
<includes>
<include>com.qsl.Hello</include>
</includes>
</relocations>
</configuration>
然后打包得到 uber jar(qsl-a.jar),项目依赖从 A.jar 更改成 qsl-a.jar,B.jar 依赖继续保留,那么项目中可用的 Hello.class 就包括
com.qsl.Hello(B.jar)
com.qsla.Hello(qsl-a.jar)
问题是不是就得到解决了?更实际的案例,敬请期待我下篇博客
总结
- maven-shade-plugin 的输入目标是
项目原始jar
以及项目依赖的所有jar
,而输出目标是uber-jar
,所以 maven-shade-plugin 的规则对项目原始jar
是无效的 minimizeJar
针对的只是class
,其他类型的文件不受此约束- 同 class 共存问题,可以利用 maven-shade-plugin 的 Relocating Classes 功能,将其中一个或多个 jar 重新打包成新的 jar,保证类名相同但包名不同,然后项目依赖新的 jar,变相解决了同 class 共存问题
- 示例项目:maven-shade-plugin-demo
maven 插件之 maven-shade-plugin,解决同包同名 class 共存问题的神器的更多相关文章
- 施用 maven shade plugin 解决 jar 或类的多版本冲突
施用 maven shade plugin 解决 jar 或类的多版本冲突 使用 maven shade plugin 解决 jar 或类的多版本冲突java 应用经常会碰到的依赖的三方库出现版本 ...
- Windows10下安装Maven以及Eclipse安装Maven插件 + 创建Maven项目
在官网下载Maven http://maven.apache.org/download.cgi 下载下来后加压缩,将apache-maven-3.5.4文件夹复制到想要存放它的位置,我放在了 ...
- eclipse maven插件创建maven项目
1.下载好maven压缩包http://maven.apache.org/ ,解压后放到想要安装的目录,如E:/server/maven,然后配置好maven环境变量,找到maven安装目录下conf ...
- myeclipse使用maven插件进行maven install时报错check $m2_home environment variable and mvn script match
check $m2_home environment variable and mvn script match 分类: maven2015-09-01 18:06 842人阅读 评论(0) 收藏 举 ...
- m2eclipse(maven插件)报错解决
在eclipse中安装了m2eclipse(maven插件) 的问题结解决 在安装后,出现下列警告: The Maven Integration requires that Eclipse be ru ...
- 解决Eclipse Maven插件的最佳方案
最近在尝试使用GAE,要求项目必须使用Maven,于是下载了Maven3.1.1配置了环境变量.但是在为Eclipse EE安装Maven插件的时候遇到了问题. 官网的建议是: 在Eclipse EE ...
- 【spring cloud】【IDEA】【maven】spring cloud多模块在idea上使用maven插件打包报错:程序包XXX不存在
>>>>spring cloud 多模块 >>>>在idea上使用maven插件打包,欲打包成jar包后 进行部署 >>>> 报 ...
- [原]Eclipse 安装SVN、Maven插件(补充)
参考雨之殇的文章:Eclipse 安装SVN.Maven插件 1.SVN可以按文章介绍的正常安装 2.Maven的Eclipse插件地址有变化 文章中的安装链接已经失效:m2e - http://m2 ...
- Gradle 1.12用户指南翻译——第五十二章. Maven 插件
本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见:http://blog.csdn.net/column/details/gradle-translation.html翻译项目请关注Github上 ...
- maven 学习---Maven 插件
什么是 Maven 插件? Maven 实际上是一个依赖插件执行的框架,每个任务实际上是由插件完成.Maven 插件通常被用来: 创建 jar 文件 创建 war 文件 编译代码文件 代码单元测试 创 ...
随机推荐
- 3562-IgH EtherCAT主站开发案例
- Excel 更改数据同步更新到Mysql数据库
刚上班,领导给我提出一个需求,想要每天更新Mysql数据库中的原有商品订单状态,添加新的商品订单状态.因为公司目前的数据库只能添加数据,不能更改数据,想要更改原有的数据,只能将原有的数据清空,再导入新 ...
- debian12 安装ch343驱动
前言 最近心血来潮,装了一台debian12玩,安装完毕arduino后发现没有ch343驱动,倒是在 ls /lib/modules/6.1.0-13-amd64/kernel/drivers/us ...
- SpringBoot如何解决跨域问题
什么是跨域 跨域问题的本质是浏览器为了保证用户的一种安全拦截机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据. 跨域三种情况 在发起请求时,如果出现了以下情况中的任意一种,那么它就是跨域请求: ...
- new操作符具体干了什么呢?
new操作符的作用如下: 1.创建一个空对象2.由this变量引用该对象3.该对象继承该函数的原型4.把属性和方法加入到this引用的对象中5.新创建的对象由this引用,最后隐式地返回this.过程 ...
- c++临时对象导致的生命周期问题
对象的生命周期是c++中非常重要的概念,它直接决定了你的程序是否正确以及是否存在安全问题. 今天要说的临时变量导致的生命周期问题是非常常见的,很多时候没有一定经验甚至没法识别出来.光是我自己写.rev ...
- 「Pygors跨平台GUI」1:Pygors跨平台GUI应用研究
「Pygors系列」一句话导读: Python.Go.Rust.C程序跨平台GUI框架研究. 一.问题 Pygors是什么? Pygors是我自己创造的一个词,就是Python.Go.Rust.C四种 ...
- [oeasy]python0022_框架标题的制作_banner_结尾字符串_end
结尾字符串(end) 回忆上次内容 python3 的程序是一个 5.3M 的可执行文件 python3 里面存的是 cpu 指令 可以执行的那种 我们可以把指令对应的汇编找到 ...
- Volatile不保证原子性及解决方案
原子性的意义 原子性特别是在并发编程领域,是一个极其重要的概念,原子性指的是一个操作或一组操作要么全部执行成功,要么全部不执行,不会出现部分执行的情况.这意味着原子性操作是不可分割的,它们在执行过程中 ...
- Vue 基于vue-codemirror实现的代码编辑器
基于vue-codemirror实现的代码编辑器 开发环境 jshint 2.11.1 jsonlint 1.6.3 script-loader 0.7.2 vue 2.6.11 vue-codemi ...