目录
背景
在maven项目中引入新的第三方组件时,组件中的依赖可能会与项目已有组件依赖的jar包(其他组件)发生冲突。
比如新添加的milvus-sdk-java 是2.0.3,依赖的 protobuf-java 版本得是3.12.0;而项目中已有的hbase版本是1.2.0.x,依赖的 protobuf-java 版本得是2.5.0。 那么此时就会产生一下两种情况:
- 如果用三方组件的高版本httpclient覆盖原有的低版本httpclient,有可能会导致原来项目启动运行失败。即使高版本兼容低版本,也不能允许开发人员有这样高风险的操作
- 如果在三方maven依赖中对其对依赖的httpclient在引入时使用进行排除,使三方组件使用项目中的低版本httpclient,此时可能会因为版本不一致导致三方组件无法使用
比如 直接运行会报错:
java.lang.NoClassDefFoundError: Could not initialize class org.apache.hadoop.hbase.util.ByteStringer
缘由:
在HBase 1.2.X版本及以前的版本HBase是强依赖于protobuf-2.5.0,若是在依赖中引入3.0.0以上版本的protobuf,其中一个类LiteralByteString从包内可访问可继承的 变为静态内部类,不可被继承。导致初始化失败。【还有其他的原因,比如方法的签名改变了等等】
在这样的情况下我们应当如何保证不影响项目原有依赖版本的情况下正常使用三方组件呢?此时可以考虑使用maven-shade-plugin插件:
maven-shade-plugin介绍
maven-shade-plugin在maven官方网站中提供的一个插件,官方文档中定义其功能如下:
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.
简单来说就是将依赖的包在package阶段一起打入jar包中,以及对依赖的jar包进行重命名从而达到隔离的作用。这里为了解决上面的问题我们主要使用第二个功能特性,使得相同依赖不同版本达到共存的目的。
解决问题
1.环境准备
事实上,集成度比较高的jar包,里面的依赖链又大又深,一般会有多个问题。比如上述例子中除了protobuf-java,还会有 guava、grpc-core等jar包也会存在版本冲突。但本文重点讲如何解决问题,就挑其中一个具体讲解:
<!-- 原项目 -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.15.0</version>
</dependency>
新引入一个三方依赖 milvus-sdk-java,该依赖使用3.12.0版本的 protobuf-java
<!-- 新项目 -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.12.0</version>
</dependency>
2.解决方案
因为项目中没有直接操作 protobuf-java, 都是“依赖的依赖”,所以不能在一个项目里自定义的区分选择同一个jar包的某一个版本。
所以 原项目中已依赖的hbase 不动它,新建一个项目ds-rec-milvus,专门用来存放milvus和它依赖的jar包,然后添加maven-shade-plugin插件。
ds-rec-milvus的pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ds.rec</groupId>
<artifactId>ds-rec-milvus</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>io.milvus</groupId>
<artifactId>milvus-sdk-java</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<relocations>
<relocation>
<pattern>com.google.protobuf</pattern>
<shadedPattern>shade.com.google.protobuf</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- 发布位置配置 -->
<distributionManagement>
<repository>
<id>xxx</id>
<name>xxx</name>
<url>http://xx</url>
</repository>
<snapshotRepository>
<id>xxx-snapshot</id>
<name>xxx-snapshot</name>
<url>http://xxxx</url>
</snapshotRepository>
</distributionManagement>
</project>
从配置文件中可以看到,由于maven-shade-plugin插件在解决这个问题上其实是通过对依赖进行重命名而达到隔离的目的,所以配置主要是集中在relocations中。这里将以shade.com.google.protobuf 开头的包全部重命名为以shade.com.google.protobuf开头。
3.引入依赖
将ds-rec-milvus进行打包,打包好之后在原项目中引入ds-rec-milvus的依赖。
<!-- 自定义jar包,解决与hbase依赖的版本冲突 -->
<dependency>
<groupId>com.rec</groupId>
<artifactId>rec-ds-milvus</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
此时可以看到依赖从milvus-sdk-java-2.0.3 变为了ds-rec-milvus-1.0.0-SNAPSHOT
此时再双击shift,搜索冲突的替罪羔羊:ByteStringer,就只保留了hbase依赖的内容
运行milvus的测试类,终于成功了~
一些需要注意的坑
集成度比较高的jar包,里面的依赖链又大又深,一般会有多个问题。比如上述例子中除了protobuf-java,还会有 guava、grpc-core等jar包也会存在版本冲突
所以建议:
先新建一个新项目,无干扰的测试此方案是否可行。可行后,再引入原来的“屎山”,逐个问题去排查。最终让大家伙动起来~~
maven-shade-plugins的其他使用
- 打入和排除指定jar包。maven-shade-plugins还有个功能就是打入和排除指定的jar包,通过和指定。
官方配置示例 【包含式 打入】
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>classworlds:classworlds</exclude>
<exclude>junit:junit</exclude>
<exclude>jmock:*</exclude>
<exclude>*:xml-apis</exclude>
<exclude>org.apache.maven:lib:tests</exclude>
<exclude>log4j:log4j:jar:</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
官方配置示例【又包又排的 打入】
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>junit:junit</artifact>
<includes>
<include>junit/framework/**</include>
<include>org/junit/**</include>
</includes>
<excludes>
<exclude>org/junit/experimental/**</exclude>
<exclude>org/junit/runners/**</exclude>
</excludes>
</filter>
<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>
...
</project>
- 排除包内资源。在上面的pom中使用maven-shade-plugin时,使用来对包内META-INF下的一些资源进行排除。如上面的配置中排除META-INF下的资源文件
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>