使用maven-shade-plugin relocation解决包冲突

young 855 2021-11-10

项目中使用了org.springframework.cglib.beans.BeanCopier进行JavaBean的复制操作,此操作不会进行类型转换操作,及类型不一致时,属性无法赋值,包括基本类型和包装类型,需要使用Converter进行类型转换。

查看Converter时发现没有参数名的描述,于是在github上查询Spring-core的源码,发现源码与Jar中文件差异很大

spring-core.jar
springcore.jar.png

spring-core源码
springcoregithub.png

许多jar中的class文件在源码中不存在,怀疑为打包时进行了某些操作,于是查询spring-core.gradle文件

springcoregradle.png

spring-core 包括 asm 和重新打包 cglib,将两者内联到 spring-core jar 中。
cglib 本身依赖于 asm,因此被 ShadowJar 任务进一步转换为
依赖于 org.springframework.asm; 这避免了包含两个不同的 asm 副本。

由此可见spring-core在打包时,将asm和cglib的包迁移到了自身的jar中,通过gradle的relocate操作

经查询,maven也有相似的功能,通过maven-shade-plugin操作

使用方法:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.1.1</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <relocations>
                    <relocation>
                    	<!-- 源包名 -->
                        <pattern>com.google.common</pattern>
                        <!-- 目的包名 -->
                        <shadedPattern>org.example.google.common</shadedPattern>
                    </relocation>                    
                </relocations>
            </configuration>
        </execution>
    </executions>
</plugin>

测试

guava 26-jre版本中存在com.google.common.base.Strings.lenientFormat方法,此方法在guava 19版本中是不存在的

创建3个项目用于测试

  1. guava19项目,引入guava19版本
     <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
             <version>19.0</version>
     </dependency>
    
  2. guava26项目,引入guava26-jra版本
    <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>26.0-jre</version>
    </dependency>
    
  3. test-relocation-main项目,引入guava19与guava26项目
    	<dependency>
             <groupId>org.example</groupId>
             <artifactId>guava19</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.example</groupId>
             <artifactId>guava26</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
    

在guava19项目中创建一个测试类

public class Guava19Test {
    
    public static void test(){
        Objects.toStringHelper(String.class);
    }
}

测试该方法,可正确执行

在guava26项目中创建一个测试类

public class Guava26Test {
    public static void  test(){
        Strings.lenientFormat("asd","xxcz");
    }
}

测试该方法,可正确执行

在test-relocation-main中执行两个测试方法

public class App {
	public static void main(String[] args) {
		Guava19Test.test();
		Guava26Test.test();
	}
}

此时会抛出异常Exception in thread "main" java.lang.NoSuchMethodError: com.google.common.base.Strings.lenientFormat(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;

Strings.lenientFormat方法没有找到
查看maven依赖关系
testrelocationpom.png

可见此时test-relocation-main中的guava使用的是19版本

如果排除掉19版本的依赖

<dependency>
	<groupId>org.example</groupId>
        <artifactId>guava19</artifactId>
        <version>1.0-SNAPSHOT</version>
        <exclusions>
            <exclusion>
                <artifactId>guava</artifactId>
                <groupId>com.google.guava</groupId>
            </exclusion>
        </exclusions>
</dependency>
        <dependency>
        <groupId>org.example</groupId>
        <artifactId>guava26</artifactId>
        <version>1.0-SNAPSHOT</version>
</dependency>

再去执行测试代码,此时会提示Exception in thread "main" java.lang.NoSuchMethodError: com.google.common.base.Objects.toStringHelper(Ljava/lang/Class;)Lcom/google/common/base/Objects$ToStringHelper;

在guava19的build中添加shade插件

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.1.1</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <relocations>
                    <relocation>
                        <!-- 源包名 -->
                        <pattern>com.google.common</pattern>
                        <!-- 目的包名 -->
                        <shadedPattern>org.example.google.common</shadedPattern>
                    </relocation>
                </relocations>
            </configuration>
        </execution>
    </executions>
</plugin>

然后执行打包命令 mvn clean install -Dmaven.test.skip=true
执行完成,刷新maven依赖
再去test-relocation-main的测试类中执行测试方法,此时测试可通过
查看依赖的jar,可见我们自己的包中已经将相关的文件复制了过来
testrelocationjar.png

参考:

https://www.jianshu.com/p/a3ed849610ad

https://blog.csdn.net/ifenggege/article/details/108327167