You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何像CraftBukkit加载插件一样加载多Jar中的外部化Module类

核心实现思路:基于URLClassLoader的同JVM类加载

你目前用java -jar启动模块的逻辑本质是开启了独立JVM进程,天然存在类隔离问题,才需要拷贝主类代码。类似CraftBukkit的插件加载逻辑完全在同一个JVM进程内实现,通过自定义类加载器加载外部jar类即可解决依赖问题。

具体实现步骤
  • 第一步:抽离公共API包
    将所有模块依赖的公共定义(比如Module抽象类、事件接口、通用工具类等)单独抽成独立的API jar包,主程序和所有模块都仅依赖这个API包即可。只要API不做破坏性更新,模块完全不需要绑定主程序的具体版本,彻底解决重复拷贝主类代码的问题。
  • 第二步:重写模块加载逻辑,废弃独立进程启动方案
    主程序直接扫描modules目录下的jar文件,通过URLClassLoader在当前JVM进程内加载模块类,参考代码如下:
// 扫描modules目录下的所有jar包
File moduleDir = new File("modules");
File[] jarFiles = moduleDir.listFiles((dir, name) -> name.endsWith(".jar"));
if (jarFiles == null) return;

List<Module> loadedModules = new ArrayList<>();
for (File jar : jarFiles) {
    URL jarUrl = jar.toURI().toURL();
    // 传入主程序的类加载器作为父加载器,模块可直接访问主程序/公共API的所有类
    URLClassLoader moduleClassLoader = new URLClassLoader(new URL[]{jarUrl}, this.getClass().getClassLoader());
    
    // 方案1:约定所有模块主实现类的全限定名,比如固定为com.xxx.module.Main
    // 方案2:参考Bukkit的plugin.yml,在模块jar的META-INF下新增配置文件,写入模块主类路径,加载时先读配置获取类名
    Class<?> moduleImplClazz = moduleClassLoader.loadClass("com.xxx.module.Autorole");
    // 实例化后转为公共抽象类Module
    Module moduleInstance = (Module) moduleImplClazz.getDeclaredConstructor().newInstance();
    loadedModules.add(moduleInstance);
}
// 加载完成后将模块实例放入主程序的调用列表即可
  • 第三步:实现热更新逻辑
    热更时先将旧模块实例从主程序调用列表中移除,调用Module抽象类约定的destroy()方法释放旧模块占用的资源,关闭旧的URLClassLoader,删除旧jar包放入新jar包,再重新执行扫描加载逻辑即可生效。
依赖问题说明

创建URLClassLoader时传入了主程序的类加载器作为父加载器,遵循JDK双亲委派模型,模块加载类时会优先由父加载器(主程序类加载器)加载,所以模块中用到的Module抽象类、主程序公共类都能直接找到,模块编译期只要依赖公共API包即可,不需要引入主程序的代码。

内容的提问来源于stack exchange,提问作者Stormer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 15:00:05