通过自定义URLClassLoader打破双亲委派,为每个Jar分配独立类加载器,结合SPI接口实现类隔离与动态调用,确保同名类可共存且类型安全。

核心思路是让每个版本的 Jar 包由独立的类加载器实例加载,从而在 JVM 中形成互不干扰的类空间。
理解类唯一性标识
JVM 判定两个类是否相同,不仅看全限定名,更关键的是“类加载器 + 类名”。即使 com.example.Service 在 v1.jar 和 v2.jar 中字节码完全一样,只要被不同 ClassLoader 实例加载,它们就是两个独立的类,可共存、可并行使用,不会触发 NoClassDefFoundError 或 ClassCastException。
打破双亲委派,构建隔离环境
默认的 AppClassLoader 会把加载请求层层上抛,最终导致所有 Jar 都走同一路径。要实现隔离,必须绕过该机制:
- 继承 URLClassLoader,重写 loadClass(String, boolean) 方法
- 优先调用 findClass(name) 尝试从插件 Jar 加载,失败后再委托 parent(如需基础类支持)
- 构造时显式传入 null 或指定父加载器,避免默认继承上下文类加载器
- 确保插件 Jar 中的类及其依赖(如内部工具类、配置类)全部由该 ClassLoader 加载,防止传导泄漏
动态加载与安全调用
加载后不能直接 new 实例或强转接口,因为类型系统不互通:
- 定义统一的 SPI 接口(如 Executor),放在主应用 classpath 下,由系统类加载器加载
- 插件 Jar 中实现该接口,但具体类由自定义 ClassLoader 加载
- 通过反射获取 Class.forName("ImplClass", true, pluginClassLoader)
- 再用 newInstance() 或 Constructor.newInstance() 创建对象
- 最后通过接口引用调用方法,避免类型强转异常
工程结构与部署建议
推荐分层组织,降低耦合:
- common 模块:只放接口、DTO、常量等无实现的轻量代码,供主程序和所有插件共享
- v1-plugin / v2-plugin:各自打包为独立 Jar,不含 common 依赖(避免重复类)
- proxy 模块:持有多个 ClassLoader 实例,按业务路由选择对应插件执行
- 插件 Jar 存放于外部目录(如 plugins/v1/),运行时通过 File.toURI().toURL() 构建 URL 数组传入 ClassLoader










