maven 插件之 maven-shade-plugin,解决同包同名 class 共存问题的神器

这段话简明扼要的概述了 maven-shade-plugin 的功能

  1. 能够将项目连同其依赖,一并打包到一个 uber-jar 中

    uber-jar 就是一个超级 jar,不仅包含我们的工程代码,还包括依赖的 jar,和 spring-boot-maven-plugin 类似

  2. 能够对依赖 jar 中的包名进行重命名

    这个功能就有意思了,后面我们详说

maven-shade-plugin 必须和 Maven 构建生命周期的 package 阶段绑定,那么当 Maven 执行 mvn package 时会自动触发 maven-shade-plugin;使用很简单,在 pom.xml 添加该插件依赖即可

 

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,其项目结构如下

如果不做任何剔除,可以按如下配置进行全打包

 

xml

代码解读

复制代码

<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

 

xml

代码解读

复制代码

<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 配置,我们需要明白其每个子标签的含义

  1. filter:过滤器,可以配置多个

  2. artifact:复合标识符,用来匹配 jar,简单点说,就是匹配 jar 的 匹配规则

    按 Maven 的坐标:groupId:artifactId[[:type]:classifier] 进行配置,groupId:artifactId 必配,[[:type]:classifier] 选配;支持通配符 * 和 ?,例如:<artifact>*:*</artifact>(相当于匹配上所有jar)

  3. exclude:排除项,也就是不会复制进 uber-jar;支持通配符配置

  4. include:包含项,也就是只有这些会被复制进 uber-jar;支持通配符配置

我们实战下,假设我们项目结构如下所示

configuration 配置如下

 

xml

代码解读

复制代码

<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 配置如下

 

xml

代码解读

复制代码

<configuration> <minimizeJar>true</minimizeJar> </configuration>

我们在 StringUtil 中引入 hutool 的 StrUtil(相当于项目依赖了 StrUtil)

 

java

代码解读

复制代码

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,是不是按如下配置?

 

xml

代码解读

复制代码

<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 默认排除行为

 

xml

代码解读

复制代码

<filter> <artifact>cn.hutool:hutool-all</artifact> <excludeDefaults>false</excludeDefaults> <includes> <include>cn/hutool/json/**</include> </includes> </filter>

这样配置之后,既能包含 hutool 的 json 包,又能包含最小依赖的 core 包

false 通常配合 true 来使用,不然

 
 

xml

代码解读

复制代码

<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,可以按如下配置

 

xml

代码解读

复制代码

<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 目录结构如下

整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记的【点击此处即可】即可免费获取

我们来看下 uber-jar 中的 StringUtil.class

依赖的 StrUtil 也被正确调整了,是不是很牛皮?

此时项目A 依赖 B.jar 的同时,又依赖我们的 maven-shade-plugin-demo,就不会类重名了(package 不一致了)

relocation 同样支持 exclude 和 include

 

xml

代码解读

复制代码

<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 (生成附属包)功能

 

xml

代码解读

复制代码

<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

这个就比较简单了,我们直接看配置

 

xml

代码解读

复制代码

<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 自定义属性

 

xml

代码解读

复制代码

<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

services_proxyFactory

我们也自定义一个

自定义QslFactory

configuration 配置如下

 

xml

代码解读

复制代码

<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 文件内容差异如下

services_合并后前后对比

如果不配置 ServicesResourceTransformer,结果是怎样,你们自己去试

AppendingTransformer

将多个同名文件的内容合并追加到一起(不配置的情况下会覆盖,最终文件内容只是其中某个文件的内容),configuration 配置如下

 

xml

代码解读

复制代码

<configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.factories</resource> </transformer> </transformers> </configuration>

打包后文件内容合并如下

Append转换器

XmlAppendingTransformerResourceBundleAppendingTransformer 功能类似,只是针对的文件内容格式略微有点特殊,就不演示了,你们自行去测试

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值