Java类加载器隔离的核心是ClassLoader实例+全限定名共同决定类唯一性;需切断双亲委派、为每版本创建独立加载器实例,并将公共接口与插件实现分层加载。

关键不在“重写多少逻辑”,而在于控制加载入口、切断默认委托链、确保类加载器实例独立——只要每个版本对象由专属 ClassLoader 加载,其静态变量、类型身份、方法区数据就天然隔离。
明确类唯一性:ClassLoader 实例 + 类全名 = 独立类型
JVM 判定两个类是否相同,看的是类全限定名 + 加载它的 ClassLoader 实例。哪怕 v1.jar 和 v2.jar 里都有 com.example.MessageSender,只要分别由两个不同的 URLClassLoader 实例加载,它们就是完全无关的两个类:
- 彼此不能强转(
ClassCastException) - 各自持有独立的静态字段(
private static int counter互不干扰) - 可同时存在于同一 JVM,被不同线程安全调用
切断双亲委派:重写 loadClass,本地优先,拒绝上抛
默认 loadClass 会先调父加载器,导致高版本类被低版本 ClassLoader 委托给 AppClassLoader 加载,彻底失效隔离。必须覆盖逻辑:
- 开头查缓存:
findLoadedClass(name),命中直接返回 - 未命中则跳过父委托,直调
findClass(name)从指定路径(如/plugins/kafka-0.10/)读取字节码 - 仅当
findClass明确抛出ClassNotFoundException时,才考虑调super.loadClass - 对
java.*、javax.*等核心类,仍可保留委托(避免破坏 JVM 基础)
实例隔离:每个版本配一个 ClassLoader 对象,不可复用
定义一个 PluginClassLoader 类远远不够。必须为每个版本创建新实例:
ClassLoader v1Loader = new PluginClassLoader("/plugins/mysql-5.1/");ClassLoader v2Loader = new PluginClassLoader("/plugins/mysql-8.0/");- 两者加载的
com.mysql.jdbc.Driver是不同类,其static初始化块各执行一次,static字段完全隔离 - 若共用一个实例,所有版本都会被塞进同一个命名空间,隔离失效
接口与实现分离:公共契约走系统加载器,实现体走插件加载器
避免运行时类型不匹配,需分层加载:
- 定义统一 SPI 接口(如
MessageService),打包进主程序,由 AppClassLoader 加载 - v1 和 v2 的
MessageServiceImpl分别打包在各自插件 jar 中,由对应 ClassLoader 加载 - 通过反射或
Class.forName(..., true, v1Loader)获取实现类,再强制转型为接口(安全,因接口是共享的) - 这样既能多版本并行,又无需修改调用方代码










