Java自定义ClassLoader实现插件类隔离加载
为什么需要类隔离加载
项目开发过程中,需要依赖不同版本的中间件依赖包,以适配不同的中间件服务端
如果这些中间件依赖包版本之间不能向下兼容,高版本依赖无法连接低版本的服务端,相反低版本依赖也无法连接高版本服务端
项目中也不能同时引入两个版本的中间件依赖,势必会导致类加载冲突,程序无法正常执行
解决方案
1、插件包开发:将不同版本的依赖做成不同的插件包,而不是直接在项目中进行依赖引入,这样不同的依赖版本就是不同的插件包了
2、插件包打包:将插件包打包时合入所有的三方库依赖
3、插件包加载:主程序根据中间件版本加载不同的插件包即可执行业务逻辑即可
插件包开发
此处以commons-lang3依赖举例
新建Maven项目,开发插件包,引入中间件依赖,插件包里面依赖的版本是3.11
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
获取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配置如下:
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
打包后查看xxx-jar-with-dependencies.jar包结构

主程序加载插件包
主程序依赖commons-lang3的3.12.0版本
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
类加载器的双亲委派机制,先使用父加载器加载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
Java自定义ClassLoader实现插件类隔离加载的更多相关文章
- Java虚拟机JVM学习02 类的加载概述
Java虚拟机JVM学习02 类的加载概述 类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对 ...
- java 27 - 1 反射之 类的加载器
说到反射,首先说类的加载器. 类的加载: 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化. 加载: 就是指将class文件读入内存,并为之 ...
- Java温故而知新(10)类的加载机制
类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指导开发者采取更有效的措施配合程序执行. 研究类加载机制的第二个目的是让程序能动态的控制类加载,比如热部署等,提高程序的灵活性 ...
- 透过现象看本质:Java类动态加载和热替换
摘要:本文主要介绍类加载器.自定义类加载器及类的加载和卸载等内容,并举例介绍了Java类的热替换. 最近,遇到了两个和Java类的加载和卸载相关的问题: 1) 是一道关于Java的判断题:一个类被首次 ...
- java类的加载机制
什么是类装载器ClassLoader ClassLoader是一个抽象类 ClassLoader的实例将读入Java字节码将类装载到JVM中 ClassLoader可以定制,满足不同的字节码流获取方式 ...
- java类的加载、链接、初始化
JVM和类的关系 当我们调用JAVA命令运行某个java程序时,该命令将会启动一条java虚拟机进程,不管该java程序有多么复杂,该程序启动了多少个线程,它们都处于该java虚拟机进程里.正如前面介 ...
- java 类的加载、连接和初始化
JVM和类 调用Java命令运行Java程序时,该命令将会启动一条Java虚拟机进程,不管该Java程序启动了多少条线程,创建了多少个变量,它们都处于该Java虚拟机进程里,共享该JVM进程的内存区. ...
- <JVM中篇:字节码与类的加载篇>04-再谈类的加载器
笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...
- 别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析【JVM篇二】
目录 1.什么是类的加载(类初始化) 2.类的生命周期 3.接口的加载过程 4.解开开篇的面试题 5.理解首次主动使用 6.类加载器 7.关于命名空间 8.JVM类加载机制 9.双亲委派模型 10.C ...
- java 反射,类的加载过程以及Classloader类加载器
首先自定义一个类Person package reflection; public class Person { private String name; public int age; public ...
随机推荐
- 《SQL与数据库基础》02. SQL-DDL
目录 DDL 库管理 表管理 本文以 MySQL 为例 DDL 库管理 查看有哪些数据库: SHOW DATABASES; 使用某个数据库: USE 数据库名; 查看当前使用的数据库: SELECT ...
- Vue源码学习(一):数据劫持(对象类型)
好家伙,了解一下Vue如何实现数据劫持 1.Vue中data的使用 首先,我得搞清楚这玩意的概念,我们先从vue的使用开始吧 想想看,我们平时是如何使用vue的data部分的? 无非是这两种情况 (你 ...
- 2023-09-03:用go编写。给你一个 n 个节点的无向无根树,节点编号从 0 到 n - 1 给你整数 n 和一个长度为 n - 1 的二维整数数组 edges , 其中 edges[i] =
2023-09-03:用go语言编写.给你一个 n 个节点的无向无根树,节点编号从 0 到 n - 1 给你整数 n 和一个长度为 n - 1 的二维整数数组 edges , 其中 edges[i] ...
- Tongweb远程调试
最近,在对项目进行国产化时,要求springboot项目容器换成tongweb.在部署中,有个问题一直无法在本地重现,搜了下网上资料,基本没法实现,所以我整理了下.注意,项目包代码必须与本地代码保持一 ...
- 重磅| Falcon 180B 正式在 Hugging Face Hub 上发布!
引言 我们很高兴地宣布由 Technology Innovation Institute (TII) 训练的开源大模型 Falcon 180B 登陆 Hugging Face! Falcon 180B ...
- Electron创建项目并打包生成exe
安装nodejs 访问这个网站去下载 http://nodejs.cn/download/ 创建项目 创建项目 git clone https://github.com/electron/electr ...
- gitbook生成静态页面不跳转
gitbook页面不跳转 现在可以在localhost:4000/查看自己的网页了.而且生成的网页存在_book文件夹中,下次点击 _book文件夹中的index.html就能打开网页,内容无更新,就 ...
- 实战0-1,Java开发者也能看懂的大模型应用开发实践!!!
前言 在前几天的文章<续写AI技术新篇,融汇工程化实践>中,我分享说在RAG领域,很多都是工程上的实践,做AI大模型应用的开发其实Java也能写,那么本文就一个Java开发者的立场,构建实 ...
- 软件开发人员 Kubernetes 入门指南|Part 2
在第 1 部分中,我们讲解了 Kubernetes 的核心组件,Kubernetes 是一种开源容器编排器,用于在分布式环境中部署和扩展应用程序:我们还讲解了如何在集群中部署一个简单的应用程序,然后更 ...
- 14.9 Socket 高效文件传输
网络上的文件传输功能也是很有必要实现一下的,网络传输文件的过程通常分为客户端和服务器端两部分.客户端可以选择上传或下载文件,将文件分块并逐块发送到服务器,或者从服务器分块地接收文件.服务器端接收来自客 ...