Java自定义ClassLoader实现插件类隔离加载 您所在的位置:网站首页 urlclassloader加载资源文件 Java自定义ClassLoader实现插件类隔离加载

Java自定义ClassLoader实现插件类隔离加载

2024-04-21 12:11| 来源: 网络整理| 查看: 265

为什么需要类隔离加载

 项目开发过程中,需要依赖不同版本的中间件依赖包,以适配不同的中间件服务端

如果这些中间件依赖包版本之间不能向下兼容,高版本依赖无法连接低版本的服务端,相反低版本依赖也无法连接高版本服务端

项目中也不能同时引入两个版本的中间件依赖,势必会导致类加载冲突,程序无法正常执行

 

解决方案

1、插件包开发:将不同版本的依赖做成不同的插件包,而不是直接在项目中进行依赖引入,这样不同的依赖版本就是不同的插件包了

2、插件包打包:将插件包打包时合入所有的三方库依赖

3、插件包加载:主程序根据中间件版本加载不同的插件包即可执行业务逻辑即可

 

插件包开发

此处以commons-lang3依赖举例

新建Maven项目,开发插件包,引入中间件依赖,插件包里面依赖的版本是3.11

org.apache.commons commons-lang3 3.11

 获取commons-lang3的StringUtils类全路径,代码如下:

public class PluginProvider { public void test() { // 获取当前的类加载器 System.out.println("Plugin: " + this.getClass().getClassLoader()); // 获取类全路径 System.out.println("Plugin: " + StringUtils.class.getResource("").getPath()); } }

 

插件包打包

 使用maven-assembly-plugin打包插件,将所有依赖包中的class文件打包到Jar包中,pom.xml配置如下:

org.apache.maven.plugins maven-compiler-plugin 1.8 1.8 maven-assembly-plugin jar-with-dependencies make-assembly package single

打包后查看xxx-jar-with-dependencies.jar包结构

 

主程序加载插件包

主程序依赖commons-lang3的3.12.0版本

org.apache.commons commons-lang3 3.12.0

 

类加载器的双亲委派机制,先使用父加载器加载class,加载不到时再调用findClass方法

这里我们直接将父加载器设置为NULL,插件包类引用的所有Class重新进行加载,类加载器重构代码如下:

public class PluginClassLoader extends URLClassLoader { public PluginClassLoader(URL[] urls) { // 类加载器的双亲委派机制 // 先使用父加载器加载class,加载不到时再调用findClass方法 super(urls, null); } }

将插件包放在/resources/plugin/目录中,如图所示:

 

调用插件包代码如下:

public class PluginTester { @PostConstruct public void test() { // 打印当前类加载器 System.out.println("Boot: " + this.getClass().getClassLoader()); // 获取StringUtils的类全路径 System.out.println("Boot: " + StringUtils.class.getResource("").getPath()); // 模拟调用插件包 testPlugin(); } public void testPlugin() { try { // 加载插件包 ClassPathResource resource = new ClassPathResource("plugin/plugin-provider.jar"); // 打印插件包路径 System.out.println(resource.getURL().getPath()); // URLClassLoader classLoader = new URLClassLoader(new URL[]{resource.getURL()}); // 初始化自己的ClassLoader PluginClassLoader pluginClassLoader = new PluginClassLoader(new URL[]{resource.getURL()}); // 这里需要临时更改当前线程的 ContextClassLoader // 避免中间件代码中存在Thread.currentThread().getContextClassLoader()获取类加载器 // 因为它们会获取当前线程的 ClassLoader 来加载 class,而当前线程的ClassLoader极可能是App ClassLoader而非自定义的ClassLoader, 也许是为了安全起见,但是这会导致它可能加载到启动项目中的class(如果有),或者发生其它的异常,所以我们在执行时需要临时的将当前线程的ClassLoader设置为自定义的ClassLoader,以实现绝对的隔离执行 ClassLoader originClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(pluginClassLoader); // 加载插件包中的类 Class clazz = pluginClassLoader.loadClass("cn.codest.PluginProvider"); // 反射执行 clazz.getDeclaredMethod("test", null).invoke(clazz.newInstance(), null); Thread.currentThread().setContextClassLoader(originClassLoader); } catch (Exception e) { e.printStackTrace(); } } }

 

执行结果如下:

// 打印主程序的类加载器Boot: sun.misc.Launcher$AppClassLoader@18b4aac2// 打印主程序中依赖的StringUtils全路径 Boot: file:/D:/Codest/Maven_aliyun/repository/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar!/org/apache/commons/lang3/// 打印插件包路径 /D:/Codest/Idea/projects/tester/plugin-boot/target/classes/plugin/plugin-provider.jar// 打印插件包中的类加载器 Plugin: cn.codest.pluginboot.PluginClassLoader@45a4b042// 打印插件包中的StringUtils全路径 Plugin: file:/D:/Codest/Idea/projects/tester/plugin-boot/target/classes/plugin/plugin-provider.jar!/org/apache/commons/lang3/

 

通过打印信息可以看出,主程序和插件包中加载的StringUtils分别来自3.12.0的Jar包和插件包中打包的3.11版本。

源码仓库:https://github.com/23557544/blog/tree/master/plugin-class-loader



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有