前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在Java里面如何解决进退两难的jar包冲突问题?

在Java里面如何解决进退两难的jar包冲突问题?

作者头像
我是攻城师
发布2019-07-27 18:06:05
3.1K0
发布2019-07-27 18:06:05
举报
文章被收录于专栏:我是攻城师

如上图所示:

es api组件依赖guava18.0,spark项目由于业务需要写入es所以需要依赖es ,但spark项目的环境又需要依赖guava14.0,如果换成高版本可能会报错,这个决定了你不能都使用统一的低版本或者高版本来规避此问题,因此必须面对现实。

导致异常的原因简单说下:

spark环境首先启动,导致jvm里面已经加载了guava14.0,这个时候jvm不会加载es依赖的guava18.0,而当es初始化的时候,恰巧需要使用guava18.0新版本的api,而这个api在14.0里面却并不存在,这个时候就会发生异常,就是我们常看到的:

代码语言:javascript
复制
java.lang.NoSuchMethodException

在深入了解一下,为什么会发生这个异常?是因为java里面的类加载器是双亲委派模式,一个类只需要在双亲委派模式下正常加载过(唯一全限定名:包名+类名)一次,就不会重复加载,从而引发了上面的问题。想要解决这种问题,靠重新再写一个类加载器是不现实的,因为重新写一个类加载器,不遵守双亲委派模式,就相当于把环境隔离了,技术上可行,但没法解决问题,如果A加载器加载的类,要调B加载器里面的类,或者B调A,会引发新的依赖问题。

那么如何比较优雅的解决这种进退两难的困境问题呢?maven-shade-plugin的出现,就可以解决这个问题的。它的解决手段也非常简单,前面说明JVM类加载器只会加载某个类一次,是通过全路径的包名+类名来区分做到的,我们要想加载不同版本的同一个类,有两种简单的方式,第一种改类名,第二种改包名。综合考虑来说改包名是最为妥当的一种方式,如果改了类名,那么要修改和替换的地方就要比改包名复杂的多了,不仅类调用的每一个地方都要替换,另外包名导入的地方也需要替换(.*导入除外,现实中不建议用这种方式),而修改包名,只需要把每一个依赖该类的类文件头部导入路径调换成新的即可,文件里面的类无需修改。

通过maven-shade-plugin插件的功能,就可以很容易做到这件事。

解法是:

单独为es的依赖创建一个maven项目,然后pom里面引入依赖的es组件,并对es组件里面依赖的guava的包名和部分组件,进行shade修改,如下:

代码语言:javascript
复制
    <groupId>es.shade</groupId>    <artifactId>es-shade</artifactId>    <version>1.0-SNAPSHOT</version>    <properties>        <elasticsearch.version>2.3.4</elasticsearch.version>    </properties>    <dependencies>        <dependency>            <groupId>org.elasticsearch</groupId>            <artifactId>elasticsearch</artifactId>            <version>${elasticsearch.version}</version>        </dependency>
    </dependencies>    <build>        <plugins>            <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-shade-plugin</artifactId>                <version>3.2.1</version>                <configuration>                    <createDependencyReducedPom>false</createDependencyReducedPom>                </configuration>                <executions>                    <execution>                        <phase>package</phase>                        <goals>                            <goal>shade</goal>                        </goals>                        <configuration>                            <relocations>                                <relocation>                                    <pattern>com.google.guava</pattern>                                    <shadedPattern>my.elasticsearch.guava</shadedPattern>                                </relocation>                                <relocation>                                    <pattern>org.joda</pattern>                                    <shadedPattern>my.elasticsearch.joda</shadedPattern>                                </relocation>                                <relocation>                                    <pattern>com.google.common</pattern>                                    <shadedPattern>my.elasticsearch.common</shadedPattern>                                </relocation>                                <relocation>                                    <pattern>com.google.thirdparty</pattern>                                    <shadedPattern>my.elasticsearch.thirdparty</shadedPattern>                                </relocation>                            </relocations>                            <transformers>                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer" />                            </transformers>                        </configuration>                    </execution>                </executions>            </plugin>        </plugins>    </build>

通过这样修改后再进行打包,那么相当于把所有guava这个改名后的组件和es的依赖在编译后的class文件层进行绑定,将其两者变成一个整体依赖jar,并且这个组件也会自动修改es里面所有导入guava的旧路径为改动后的新路径,看如下从反编译后的jar中,拷贝出来的类文件信息:

改动后的guava的MoreExecutors这个类文件头部,变成了我们修改后的包名:

代码语言:javascript
复制
package my.elasticsearch.common.util.concurrent;
import my.elasticsearch.common.collect.*;import java.lang.reflect.*;import my.elasticsearch.common.base.*;import my.elasticsearch.common.annotations.*;import java.util.concurrent.locks.*;import java.util.*;import java.util.concurrent.*;
public final class MoreExecutors{// 省略主体内容}

注意头部的包名已经变成了my.elasticsearch.common替代了原来的com.google.common。

下面我们再看下es源码里面依赖的guava路径是否变化,打开org.elasticsearch.threadpool.ThreadPool这个类,我们发现其头部已经变成了新的guava路径,如下:

代码语言:javascript
复制
package org.elasticsearch.threadpool;
import org.elasticsearch.common.component.*;import org.elasticsearch.node.settings.*;import org.apache.lucene.util.*;import my.elasticsearch.common.collect.*;import org.elasticsearch.common.*;import org.elasticsearch.common.util.concurrent.*;import org.elasticsearch.common.unit.*;import org.elasticsearch.common.collect.*;import org.elasticsearch.common.settings.*;import java.util.concurrent.*;import org.elasticsearch.common.logging.*;import my.elasticsearch.common.util.concurrent.*;import java.io.*;import org.elasticsearch.common.io.stream.*;import org.elasticsearch.common.xcontent.*;import org.elasticsearch.cluster.settings.*;import org.elasticsearch.cluster.*;import java.util.*;import java.util.regex.*;
public class ThreadPool extends AbstractComponent{
// 省略主体内容}

如此已来,这个shade jar里面的es就只对这个版本的guava进行了绑定依赖,这个时候在spark项目中,引入这个es的uber-shade-jar,就不会发生冲突,通过使用不同的包名完美解决了类冲突的问题,这两个类都可以被同一个JVM虚拟机加载,这样以来,spark仍旧可以使用guava14.0版本,而我们的es也可以完美的使用改名后的guava18.0的版本,从而比较优雅的解决了这种不可避免的多版本冲突问题。

参考链接:

https://stackoverflow.com/questions/13620281/what-is-the-maven-shade-plugin-used-for-and-why-would-you-want-to-relocate-java

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 我是攻城师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
Elasticsearch Service
腾讯云 Elasticsearch Service(ES)是云端全托管海量数据检索分析服务,拥有高性能自研内核,集成X-Pack。ES 支持通过自治索引、存算分离、集群巡检等特性轻松管理集群,也支持免运维、自动弹性、按需使用的 Serverless 模式。使用 ES 您可以高效构建信息检索、日志分析、运维监控等服务,它独特的向量检索还可助您构建基于语义、图像的AI深度应用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档