docker jar包使用环境变量_使用 classloader 解决jar包冲突

前言:

之前 《解决jar包冲突新思路 - maven-shade-plugin》 中,使用 maven-shade-plugin 解决了 guava 19.0 和 26.0-jre 的一个小冲突。当时在网上查到过关于使用 classloader 解决类似冲突的文章,大部分文章给出了理论解释,但是没有给出实际的代码例子,所以当时并没有完全 get 到。

最近有一些新的思考,于是还是用之前的例子,使用 classloader 解决一下,果然比 shade-plugin 的方式要好许多。

正文:

首先还是上代码: (下载链接:package-test,还是之前的项目,打了一个新分支),这次不需要多余的 package-test-b-shade 模块了。

再回顾一下问题:

8c6f7e8706d2bdfcf04ca4f11f9a423e.png

与上图相对应,项目总共分3个模块:a b c,其中 a 和 b 分别依赖了guava 的 19.0 和26.0.jre ,然后c同时引用了 a 和 b 。

其中guava的两个版本有下边两个不兼容的方法,用来测试:

public static Objects.ToStringHelper toStringHelper(Object self) {
        //该方法 19.0有,26.0.jre没有
}
public static String lenientFormat(
    @Nullable String template, @Nullable Object @Nullable... args) {
        //该方法 19.0没有,26.0.jre有
}

直接执行 com.zhaohui.C 的 test1() 方法,会报如下错误:

Exception in thread "main" java.lang.NoSuchMethodError: com.google.common.base.Strings.lenientFormat(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
    at com.zhaohui.B.test(B.java:9)
    at com.zhaohui.C.test1(C.java:28)
    at com.zhaohui.C.main(C.java:36)

这个分支在 package-test-a 和 package-test-b 中,分别增加了 AClassLoader 和 BClassLoader 类,并且在 C 的代码中,使用这两个类加载器加载 com.zhaohui.A 和 com.zhaohui.B 这两个类:

URLClassLoader loader = AClassLoader.getInstance();
Class<?> aClass = loader.loadClass("com.zhaohui.A");
Method method = aClass.getMethod("test");
method.invoke(null);

loader = BClassLoader.getInstance();
Class<?>  bClass = loader.loadClass("com.zhaohui.B");
method = bClass.getMethod("test");
method.invoke(null);

然后在 AClassLoader 设置guava 和 com.zhaohui.A 的加载路径:

//这里要换成你自己仓库的地址
public static final String GUAVA_19_0_PATH = "/Users/zhaohui/.m2/repository/com/google/guava/guava/19.0/guava-19.0.jar";
//这里要先把 package-test-a install 拿到地址以后,修改这个常量然后重新 install 一下
public static final String CLASS_A_PATH = "/Users/zhaohui/.m2/repository/com/zhaohui/package-test-a/1.0-SNAPSHOT/package-test-a-1.0-SNAPSHOT.jar";

private volatile static AClassLoader instance = null;

public static AClassLoader getInstance(){

    if(instance == null){
        synchronized(AClassLoader.class){
            if(instance == null){
                try{
                    URL [] urls = new URL[]{
                        new File(GUAVA_19_0_PATH).toPath().toUri().toURL()
                        ,new File(CLASS_A_PATH).toPath().toUri().toURL()
                    };
                    instance = new AClassLoader(urls,Thread.currentThread().getContextClassLoader());
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }

    return instance;

}

并且设置,只要是使用 AClassLoader 这个类加载器加载类的时候,先调用 findClass() 方法在当前类加载器中加载,加载不到的才委托给双亲。

@Override
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException {
    //优先从当前 URLClassLoader 设置的 url 中找,找不到再调用super
    Class<?> cls = null;
    try{
        cls = instance.findClass(name);
    }catch (Exception e){
    }
    if(null  != cls){
        return cls;
    }
    return super.loadClass(name,resolve);
}

BClassLoader 也做相同的设置以后,执行 com.zhaohui.C 的 test2() 方法,就不会报错了:

a28cf9631118a85b0ab6aa571c11b42c.png

如果把 AClassLoader 和 BClassloader 中写死的常量改为通过某些约定的规则或者配置获取,然后把使用反射获取对象的代码改为SpringIoC 之类的依赖注入,这个方案应该就能轻松解决大部分 jar 包冲突了吧。

以上就是使用 classloader 解决jar包冲突的方法,真正给我灵感的是类似下边的一张图:

57a0535e8df45048508b789b29a09fb2.png

总结:

我一直觉得,jar包冲突,是工作中最枯燥,也是解决起来最耽误时间,最打击人的问题。每一次有了解决这个问题的新的收获,都感觉节约了未来无数的时间~~

最后,让我们保持独立思考,不卑不亢。长成自己想要的样子! (引用自 我非常喜欢的B站up主 ”独立菌儿“->猛戳链接<-的口头禅)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值