美文网首页
利用类加载器解决不兼容的Jar包共存的问题

利用类加载器解决不兼容的Jar包共存的问题

作者: 无醉_1866 | 来源:发表于2019-10-17 00:27 被阅读0次

问题描述

应用稍复杂一点之后,往往要引入多种不同的中间件,各种第三方jar,这就导致我们往往会遇到jar包冲突的问题,如果冲突的jar包是兼容的,我们需要选择合适的版本,把不合适的版本排除掉,虽然过程复杂了点,但是如果冲突的jar包之间不兼容,那么不管选择哪个版本,都会出问题。

我们可以使用类似于OSGI这样的重框架来解决这类问题,但是这类框架太重太复杂,难以掌握,实际上我们可以利用类加载器来解决这类问题,当不兼容的情况不是很复杂时,这种方案能高效解决问题,否则需要一种插件机制来解决问题。

基本思路

类加载器双亲委派机制

这里先简单的介绍一下类加载器的双亲委派机制。JVM在加载类时默认采用的是双亲委派机制。就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;父类加载器无法完成此加载任务时,才自己去加载。我们可以看一下ClassLoader类中相关的代码:

image

解决问题思路

· 通过单独的类加载器加载依赖了不兼容的jar及中间件相关的jar,使用不同的中间件时,统一入口,用不同的类加载器加载相关的类并创建对象

· 需要打破类加载器的双亲委派机制,单独创建出的类加载器要优先自己加载,加载不到则再委派给parent加载

· 被独立的classloader所托管的jar包,不能再被原来的应用类加载器加载,否则会出现类型不匹配的异常,因为jvm判断类相同的依据是类名相同并且加载类的类加载器也相同,而被单独托管后的jar已经不是原来原始的应用类加载器了

解决方案

类的加载

从URLClassLoader派生,定义一个名为ChildFirstClassLoader的类加载器,用于加载被托管的jar,代码如下:

image

有了此类加载器后,我们需要在使用此类加载器前,找到指定的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")为例子:

image

有了此类之后,我们就解决了类的加载问题,加载类的方式如下(为了说明问题,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前文描述过,不再描述

image

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

image

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

image

相关文章

  • 利用类加载器解决不兼容的Jar包共存的问题

    问题描述 应用稍复杂一点之后,往往要引入多种不同的中间件,各种第三方jar,这就导致我们往往会遇到jar包冲突的问...

  • 创建一个java.lang.Test类,能被加载吗?

    java中类加载器有三种: Bootstrap类加载器 负责加载java的核心类包,比如rt.jar 拓展类加载...

  • JVM之类加载机制

    Android类加载器 理解类加载 Eclipse使用第三方的插件其实就是动态加载Jar包里的Class字节码进行...

  • 双亲委派模型

    一、Android类加载器 虚拟机加载类时,将二进制字节流读取到内存,可以从class文件、zip包、jar包、网...

  • ClassLoader委派加载模型

    java自带三个重要的类加载器,其组织结构如下。 1.三个类加载器的创建 是rt.jar包里面sun.misc.L...

  • java类加载

    1.类加载器1.BootStrapClassLoader 启动类加载器。加载rt.jar 以及 java. 开头...

  • 类加载器

    1.类加载器分类 1.引导类加载器:加载jdk核心类库,比如rt.jar里的类,该加载器是C语言实现,无法获取到2...

  • 3.jvm加载机制

    一、类装载子系统 加载过程 加载 预加载:加载rt.jar,里面是一些常用的类lang包和io包 运行时加载 连接...

  • 21. java虚拟机总结-类的加载机制(四)

    类的加载过程 大多数情况下,类会按照图中给出的顺序进行加载 加载 从jar包或war包找到并加载.class文件的...

  • Java-类加载器

    每个java程序至少有3个类加载器: 引导类加载器: 负责加载系统类,通常从rt.jar文件中进行加载。通常由C语...

网友评论

      本文标题:利用类加载器解决不兼容的Jar包共存的问题

      本文链接:https://www.haomeiwen.com/subject/lzbpmctx.html