如何在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
相关产品推荐
相关产品推荐

