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

如何在Bukkit服务器运行时通过插件正确程序化添加插件?

在Bukkit运行时程序化加载插件的正确方案

要解决启动后加载插件时的重复注册和依赖验证问题,你可以通过以下步骤实现:

核心思路

模拟SimplePluginManager.loadPlugins()的完整流程,但只处理未加载的新插件,同时保留依赖校验、顺序加载的逻辑。

步骤1:获取插件管理器与已加载插件记录

首先获取SimplePluginManager实例,并记录所有已加载插件的标识,避免重复处理:

// 获取SimplePluginManager实例
SimplePluginManager pluginManager = (SimplePluginManager) Bukkit.getPluginManager();
// 记录已加载插件的名称
Set<String> loadedPluginNames = new HashSet<>();
for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
    loadedPluginNames.add(plugin.getName());
}
// 记录已加载插件的文件路径,避免重复扫描
Set<File> loadedPluginFiles = new HashSet<>();
for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
    try {
        Field fileField = plugin.getClass().getDeclaredField("file");
        fileField.setAccessible(true);
        loadedPluginFiles.add((File) fileField.get(plugin));
    } catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

步骤2:扫描目标目录下的新插件文件

遍历目标目录(比如/plugins),筛选出未加载的插件.jar文件:

File pluginsDir = new File(Bukkit.getServer().getPluginManager().getPluginsFolder());
File[] pluginFiles = pluginsDir.listFiles((dir, name) -> name.endsWith(".jar"));
if (pluginFiles == null) return;

List<File> newPluginFiles = new ArrayList<>();
for (File file : pluginFiles) {
    if (!loadedPluginFiles.contains(file)) {
        newPluginFiles.add(file);
    }
}

步骤3:收集插件描述并验证依赖

先加载所有新插件的PluginDescriptionFile,然后校验依赖是否满足(包括已加载插件和即将加载的插件):

List<PluginDescriptionFile> newPluginDescs = new ArrayList<>();
// 收集所有新插件的描述文件
for (File file : newPluginFiles) {
    try {
        PluginDescriptionFile desc = pluginManager.getPluginLoader().getPluginDescription(file);
        newPluginDescs.add(desc);
    } catch (InvalidDescriptionException e) {
        e.printStackTrace();
    }
}

// 验证硬依赖是否存在
Iterator<PluginDescriptionFile> descIterator = newPluginDescs.iterator();
while (descIterator.hasNext()) {
    PluginDescriptionFile desc = descIterator.next();
    boolean dependenciesMet = true;
    for (String dep : desc.getDepend()) {
        if (!loadedPluginNames.contains(dep) && !newPluginDescs.stream().anyMatch(d -> d.getName().equals(dep))) {
            dependenciesMet = false;
            break;
        }
    }
    // 软依赖缺失仅日志提示,不阻止加载
    for (String dep : desc.getSoftDepend()) {
        if (!loadedPluginNames.contains(dep) && !newPluginDescs.stream().anyMatch(d -> d.getName().equals(dep))) {
            Bukkit.getLogger().warning("插件 " + desc.getName() + " 的软依赖 " + dep + " 未找到");
        }
    }
    if (!dependenciesMet) {
        Bukkit.getLogger().severe("插件 " + desc.getName() + " 的硬依赖缺失,无法加载");
        descIterator.remove();
        newPluginFiles.removeIf(f -> {
            try {
                return pluginManager.getPluginLoader().getPluginDescription(f).getName().equals(desc.getName());
            } catch (InvalidDescriptionException e) {
                return false;
            }
        });
    }
}

步骤4:按依赖顺序排序并加载插件

根据依赖关系对插件排序(依赖项优先加载),然后逐个加载并启用:

// 按依赖顺序排序:依赖其他插件的后加载
Collections.sort(newPluginDescs, (a, b) -> {
    if (a.getDepend().contains(b.getName())) {
        return 1;
    }
    if (b.getDepend().contains(a.getName())) {
        return -1;
    }
    return a.getName().compareTo(b.getName());
});

// 遍历排序后的描述,加载对应插件
for (PluginDescriptionFile desc : newPluginDescs) {
    File targetFile = newPluginFiles.stream()
            .filter(f -> {
                try {
                    return pluginManager.getPluginLoader().getPluginDescription(f).getName().equals(desc.getName());
                } catch (InvalidDescriptionException e) {
                    return false;
                }
            })
            .findFirst()
            .orElse(null);
    if (targetFile == null) continue;

    try {
        Plugin plugin = pluginManager.loadPlugin(targetFile);
        if (plugin == null) continue;
        plugin.onLoad();
        pluginManager.registerPlugin(plugin);
        pluginManager.enablePlugin(plugin);
        // 更新已加载记录
        loadedPluginNames.add(plugin.getName());
        loadedPluginFiles.add(targetFile);
    } catch (InvalidDescriptionException | InvalidPluginException e) {
        e.printStackTrace();
    }
}

额外注意事项

  • 如果你自定义了PluginLoader,确保在调用上述代码前已经通过pluginManager.registerInterface(YourPluginLoader.class)完成注册,Bukkit会自动使用对应的加载器处理插件。
  • 反射获取插件文件路径的方式可能因CraftBukkit版本变化失效,也可以仅通过插件名称去重(需注意避免插件重名)。
  • 加载完成后记得更新已加载记录集合,避免后续重复处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 09:26:07