为什么需要类隔离加载

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

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

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

解决方案

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实现插件类隔离加载的更多相关文章

  1. Java虚拟机JVM学习02 类的加载概述

    Java虚拟机JVM学习02 类的加载概述 类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对 ...

  2. java 27 - 1 反射之 类的加载器

    说到反射,首先说类的加载器. 类的加载: 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化. 加载: 就是指将class文件读入内存,并为之 ...

  3. Java温故而知新(10)类的加载机制

    类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指导开发者采取更有效的措施配合程序执行. 研究类加载机制的第二个目的是让程序能动态的控制类加载,比如热部署等,提高程序的灵活性 ...

  4. 透过现象看本质:Java类动态加载和热替换

    摘要:本文主要介绍类加载器.自定义类加载器及类的加载和卸载等内容,并举例介绍了Java类的热替换. 最近,遇到了两个和Java类的加载和卸载相关的问题: 1) 是一道关于Java的判断题:一个类被首次 ...

  5. java类的加载机制

    什么是类装载器ClassLoader ClassLoader是一个抽象类 ClassLoader的实例将读入Java字节码将类装载到JVM中 ClassLoader可以定制,满足不同的字节码流获取方式 ...

  6. java类的加载、链接、初始化

    JVM和类的关系 当我们调用JAVA命令运行某个java程序时,该命令将会启动一条java虚拟机进程,不管该java程序有多么复杂,该程序启动了多少个线程,它们都处于该java虚拟机进程里.正如前面介 ...

  7. java 类的加载、连接和初始化

    JVM和类 调用Java命令运行Java程序时,该命令将会启动一条Java虚拟机进程,不管该Java程序启动了多少条线程,创建了多少个变量,它们都处于该Java虚拟机进程里,共享该JVM进程的内存区. ...

  8. <JVM中篇:字节码与类的加载篇>04-再谈类的加载器

    笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...

  9. 别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析【JVM篇二】

    目录 1.什么是类的加载(类初始化) 2.类的生命周期 3.接口的加载过程 4.解开开篇的面试题 5.理解首次主动使用 6.类加载器 7.关于命名空间 8.JVM类加载机制 9.双亲委派模型 10.C ...

  10. java 反射,类的加载过程以及Classloader类加载器

    首先自定义一个类Person package reflection; public class Person { private String name; public int age; public ...

随机推荐

  1. 微软面向企业的Private ChatGPT 参考应用 Chat Copilot

    这两天你可能看过这篇文章:微软面向企业的Private ChatGPT 开源!超详细安装流程反馈![1], 这篇文章顶多就是一个Azure OpenAI 服务的简单Demo, 就连插件机制都没有,这也 ...

  2. 微信小程序预览时显示有图片未上传

    最近在做小程序项目,在项目里面加了几个图片.在预览调试时出现弹窗显示"文件未上传",但是在左侧的模拟器上却是能正常显示的. 解决思路: 图片在本地和模拟器上显示正常,表示图片本身没 ...

  3. 一个超经典 WinForm 卡死问题的最后一次反思

    一:背景 1. 讲故事 在我分析的 200+ dump 中,同样会遵循着 28原则,总有那些经典问题总是反复的出现,有很多的朋友就是看了这篇 一个超经典 WinForm 卡死问题的再反思 找到我,说 ...

  4. Python 创建或读取 Excel 文件

    Excel是一种常用的电子表格软件,广泛应用于金融.商业和教育等领域.它提供了强大的数据处理和分析功能,可进行各种计算和公式运算,并能创建各种类型的图表和可视化数据.Excel的灵活性使其成为处理和管 ...

  5. [错误] SQL logic error near "date": syntax error

    问题的来源 今天把一个项目的数据库从MySQL改到Sqlite 调试时发生了这个错误. 百度又看不懂英文(很多是国外发的), 就折腾了一下 原因 C# Sqlite 不能使用参数前缀"?&q ...

  6. 【Qt6】列表模型——抽象基类

    列表模型(Item Model),老周没有翻译为"项目模型",因为 Project 和 Item 都可以翻译为"项目",容易出现歧义.干脆叫列表模型.这个模型也 ...

  7. SQL Server用户的设置与授权

    SQL Server用户的设置与授权 SSMS 登陆方式有两种,一是直接使用Windows身份验证,二是SQL Server身份验证.使用SQL Server用户设置与授权不仅可以将不同的数据库开放给 ...

  8. Python 网页爬虫原理及代理 IP 使用

    一.Python 网页爬虫原理 Python 是一种高效的编程语言,在 Web 开发和数据分析领域广受欢迎.Python 的优秀模块使其更加适合大规模数据处理和 Web 服务的编程.网络爬虫是 Pyt ...

  9. TCP协议的秘密武器:流量控制与拥塞控制

    TCP可靠性传输 相信大家都熟知TCP协议作为一种可靠传输协议,但它是如何确保传输的可靠性呢? 要实现可靠性传输,需要考虑许多因素,比如数据的损坏.丢失.重复以及分片顺序混乱等问题.如果不能解决这些问 ...

  10. Java四种引用 强引用,软引用,弱引用,虚引用(转)

    强引用 : 只要引用存在,垃圾回收器永远不会回收 Object obj= new Object(); Object 对象对后面 new Object的一个强引用, 只有当obj这个被释放之后,对象才会 ...