osgi 引用不同版本的jar_利用类加载器解决不兼容的Jar包共存的问题

问题描述
应用稍复杂一点之后,往往要引入多种不同的中间件,各种第三方jar,这就导致我们往往会遇到jar包冲突的问题,如果冲突的jar包是兼容的,我们需要选择合适的版本,把不合适的版本排除掉,虽然过程复杂了点,但是如果冲突的jar包之间不兼容,那么不管选择哪个版本,都会出问题。
我们可以使用类似于OSGI这样的重框架来解决这类问题,但是这类框架太重太复杂,难以掌握,实际上我们可以利用类加载器来解决这类问题,当不兼容的情况不是很复杂时,这种方案能高效解决问题,否则需要一种插件机制来解决问题。
基本思路
类加载器双亲委派机制
这里先简单的介绍一下类加载器的双亲委派机制。JVM在加载类时默认采用的是双亲委派机制。就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;父类加载器无法完成此加载任务时,才自己去加载。我们可以看一下ClassLoader类中相关的代码:

10759253da9f99b99465c0d0aacc36d8.png


解决问题思路
· 通过单独的类加载器加载依赖了不兼容的jar及中间件相关的jar,使用不同的中间件时,统一入口,用不同的类加载器加载相关的类并创建对象
· 需要打破类加载器的双亲委派机制,单独创建出的类加载器要优先自己加载,加载不到则再委派给parent加载
· 被独立的classloader所托管的jar包,不能再被原来的应用类加载器加载,否则会出现类型不匹配的异常,因为jvm判断类相同的依据是类名相同并且加载类的类加载器也相同,而被单独托管后的jar已经不是原来原始的应用类加载器了
解决方案
类的加载
从URLClassLoader派生,定义一个名为ChildFirstClassLoader的类加载器,用于加载被托管的jar,代码如下:

22d8719a14371e56071af23ef23d4d91.png


有了此类加载器后,我们需要在使用此类加载器前,找到指定的jar包在系统中的路径,如何找到此路径呢,有几种方法:
1. 通过System.getProperty("java.class.path")可以拿到classpath下所有的jar路径,是一个String,不同的jar通过分号分割
2. 通过类加载器,拿到URLClassLoader中的ucp,从ucp中可以拿到当前类加载器所托管的所有jar(适用于springboot)
拿到了所有jar的路径后,即可以得到某个jar包与其在系统中的路径的对应关系,然后通过此对应关系与jar包名创建出独立的类加载器(ChildFirstClassLoader),这里以使用System.getProperty("java.class.path")为例子:

bad8b9d7a98496fc30f650aac7433686.png


有了此类之后,我们就解决了类的加载问题,加载类的方式如下(为了说明问题,jar名随意取,实际应用中的jar不方便写出来):
private final ClassContainer container = new ClassContainer(getClass().getClassLoader(), "rocketmq-client-1.1.jar", "protobuf.jar");
在创建相关对象中,使用container.getClass(className).newInstance()的方法创建对象即可
解决类型不匹配的问题
前文提到:被独立的classloader所托管的jar包,不能再被原来的应用类加载器加载,否则会出现类型不匹配的异常,因为jvm判断类相同的依据是类名相同并且加载类的类加载器也相同,而被单独托管后的jar已经不是原来原始的应用类加载器了
为了解决这个问题,我人先解释一个概念:
· 导出类:表示ClassContainer.getClass获取的类,且希望能让原始的应用类加载器加载到的类
而解决此问题有两种思路:
1. 的方式则是利用类加载器的双亲委派机制,在双亲委派机制中插入一环,无始的类加载器的parent变成新的类加载器,新的类加载器的parent设置成原来的类加载器的parent,伪代码如下:
ClassLoader cl = getClass().getClassLoader();
ExportClassLoader ecl = new ExportClassLoader(cl.getParent());
setParent(cl, ecl);//将cl的parent设置成ecl
原始的应用类加载器(cl)在加载类时,先委派给parent加载,当委派到我们人为插入的一环时(ecl),判断是否是导出类,如果是则通过ClassContainer.getClass获取,否则继续双亲委派
2. 替换掉当前的URLClassLoader,用新的类加载器替代原有的应用类加载器
在jdk1.6时,ClassLoader中的parent字段没有定义成final字段,因此第一种思路行得通,只需要在原来的应用类加载器和它的parent之间插入一个类加载器即可,如同思路1的伪代码
但从jdk1.7开始,ClassLoader的final字段被定义成了final,此时我们只能通过代理的方式解决问题,同样创建新的类加载器ExportDelegateClassLoader,整体结构如下图:
· AppClassLoader是原始的应用类加载器
· parent表示AppClassLoader的父加载器
· ExportDelegateClassLoader是新的类加载器,用于替换原始的应用类加载器AppClassLoader,ExportDelegateClassLoader与AppClassLoader的parent相同,且classpath也相同
· ClassContainer前文描述过,不再描述

fb12dc5b1a084404b4d3797238996fa3.png


代码如下图,在加载类时,先判断是否是导出类,如果是则使用对应的ClassContainer获取类,否则加载类,具体逻辑见loadClass方法。同样,此代码只是为了说明问题,并不是实际应用的代码:

d0c51b6894d1733265fead5d7e82e7fc.png


最后,在系统入口处用新的ExportDelegateClassLoader加载应用的入口类,以main方法为例:

529585d4059d9af54fec36c0b8f6ba1c.png
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值