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 ...
随机推荐
- 微软面向企业的Private ChatGPT 参考应用 Chat Copilot
这两天你可能看过这篇文章:微软面向企业的Private ChatGPT 开源!超详细安装流程反馈![1], 这篇文章顶多就是一个Azure OpenAI 服务的简单Demo, 就连插件机制都没有,这也 ...
- 微信小程序预览时显示有图片未上传
最近在做小程序项目,在项目里面加了几个图片.在预览调试时出现弹窗显示"文件未上传",但是在左侧的模拟器上却是能正常显示的. 解决思路: 图片在本地和模拟器上显示正常,表示图片本身没 ...
- 一个超经典 WinForm 卡死问题的最后一次反思
一:背景 1. 讲故事 在我分析的 200+ dump 中,同样会遵循着 28原则,总有那些经典问题总是反复的出现,有很多的朋友就是看了这篇 一个超经典 WinForm 卡死问题的再反思 找到我,说 ...
- Python 创建或读取 Excel 文件
Excel是一种常用的电子表格软件,广泛应用于金融.商业和教育等领域.它提供了强大的数据处理和分析功能,可进行各种计算和公式运算,并能创建各种类型的图表和可视化数据.Excel的灵活性使其成为处理和管 ...
- [错误] SQL logic error near "date": syntax error
问题的来源 今天把一个项目的数据库从MySQL改到Sqlite 调试时发生了这个错误. 百度又看不懂英文(很多是国外发的), 就折腾了一下 原因 C# Sqlite 不能使用参数前缀"?&q ...
- 【Qt6】列表模型——抽象基类
列表模型(Item Model),老周没有翻译为"项目模型",因为 Project 和 Item 都可以翻译为"项目",容易出现歧义.干脆叫列表模型.这个模型也 ...
- SQL Server用户的设置与授权
SQL Server用户的设置与授权 SSMS 登陆方式有两种,一是直接使用Windows身份验证,二是SQL Server身份验证.使用SQL Server用户设置与授权不仅可以将不同的数据库开放给 ...
- Python 网页爬虫原理及代理 IP 使用
一.Python 网页爬虫原理 Python 是一种高效的编程语言,在 Web 开发和数据分析领域广受欢迎.Python 的优秀模块使其更加适合大规模数据处理和 Web 服务的编程.网络爬虫是 Pyt ...
- TCP协议的秘密武器:流量控制与拥塞控制
TCP可靠性传输 相信大家都熟知TCP协议作为一种可靠传输协议,但它是如何确保传输的可靠性呢? 要实现可靠性传输,需要考虑许多因素,比如数据的损坏.丢失.重复以及分片顺序混乱等问题.如果不能解决这些问 ...
- Java四种引用 强引用,软引用,弱引用,虚引用(转)
强引用 : 只要引用存在,垃圾回收器永远不会回收 Object obj= new Object(); Object 对象对后面 new Object的一个强引用, 只有当obj这个被释放之后,对象才会 ...