第 18 章:最佳实践
第 18 章:最佳实践
汇集代码规范、性能优化、安全建议和兼容性策略,帮助你写出高质量的插件。
18.1 代码规范
命名规范
| 类型 | 规范 | 示例 |
|---|
| 包名 | 全小写,逆域名 | com.example.myplugin |
| 类名 | PascalCase | PlayerManager, EconomyService |
| 方法名 | camelCase | getBalance(), sendTitle() |
| 常量 | UPPER_SNAKE_CASE | MAX_PLAYERS, DEFAULT_LANG |
| 变量 | camelCase | playerCount, isReady |
| 配置键 | snake_case | database.host, max-players |
项目结构
com.example.myplugin/
├── MyPlugin.java # 主类
├── commands/ # 命令处理
│ ├── MainCommand.java
│ ├── HealCommand.java
│ └── WarpCommand.java
├── listeners/ # 事件监听
│ ├── PlayerListener.java
│ └── CombatListener.java
├── managers/ # 管理器
│ ├── EconomyManager.java
│ ├── WarpManager.java
│ └── ConfigManager.java
├── models/ # 数据模型
│ ├── PlayerData.java
│ └── Warp.java
├── database/ # 数据层
│ ├── DatabaseManager.java
│ └── PlayerDataDAO.java
├── utils/ # 工具类
│ ├── MessageUtil.java
│ └── ItemBuilder.java
└── tasks/ # 任务
├── AutoSaveTask.java
└── ScoreboardTask.java
设计模式
// 1. 单例模式(Singleton)
public class EconomyManager {
private static EconomyManager instance;
public static EconomyManager getInstance() {
if (instance == null) {
instance = new EconomyManager();
}
return instance;
}
}
// 2. 建造者模式(Builder)
public class ItemBuilder {
private final ItemStack item;
private final ItemMeta meta;
public ItemBuilder(Material material) {
this.item = new ItemStack(material);
this.meta = item.getItemMeta();
}
public ItemBuilder name(String name) {
meta.displayName(Component.text(name));
return this;
}
public ItemBuilder lore(String... lines) {
meta.lore(Arrays.stream(lines)
.map(Component::text)
.collect(Collectors.toList()));
return this;
}
public ItemStack build() {
item.setItemMeta(meta);
return item;
}
}
// 使用
ItemStack sword = new ItemBuilder(Material.DIAMOND_SWORD)
.name("§6传说之剑")
.lore("§7攻击 +100", "§7速度 +20%")
.build();
18.2 性能优化
高频事件优化
// 不好:每次移动都处理
@EventHandler
public void onMove(PlayerMoveEvent event) {
checkRegion(event.getPlayer());
}
// 好:只在跨越区块时处理
@EventHandler(ignoreCancelled = true)
public void onMove(PlayerMoveEvent event) {
Location from = event.getFrom();
Location to = event.getTo();
if (to == null) return;
if (from.getBlockX() >> 4 == to.getBlockX() >> 4
&& from.getBlockZ() >> 4 == to.getBlockZ() >> 4) {
return; // 同一区块,跳过
}
checkRegion(event.getPlayer());
}
数据结构选择
| 场景 | 推荐 | 避免 |
|---|
| 频繁查找 | HashMap / HashSet | ArrayList.contains() |
| 有序数据 | TreeMap / TreeSet | 手动排序 |
| 只读列表 | List.of() / Set.of() | Collections.unmodifiableXxx() |
| 大量数值 | 原生数组 (int[]) | ArrayList<Integer> |
| 并发访问 | ConcurrentHashMap | synchronized HashMap |
字符串拼接
// 不好:循环中拼接
String result = "";
for (String s : list) {
result += s + ", "; // 每次创建新 String 对象
}
// 好:使用 StringBuilder
StringBuilder sb = new StringBuilder();
for (String s : list) {
if (!sb.isEmpty()) sb.append(", ");
sb.append(s);
}
String result = sb.toString();
// 更好:使用 Stream
String result = String.join(", ", list);
缓存策略
public class CacheManager<K, V> {
private final Map<K, CacheEntry<V>> cache = new HashMap<>();
private final long ttl; // 缓存过期时间(毫秒)
public CacheManager(long ttlMillis) {
this.ttl = ttlMillis;
}
public void put(K key, V value) {
cache.put(key, new CacheEntry<>(value, System.currentTimeMillis()));
}
public V get(K key) {
CacheEntry<V> entry = cache.get(key);
if (entry == null) return null;
if (System.currentTimeMillis() - entry.timestamp() > ttl) {
cache.remove(key); // 过期移除
return null;
}
return entry.value();
}
public void clear() {
cache.clear();
}
private record CacheEntry<V>(V value, long timestamp) {}
}
实体管理
// 限制实体数量
public void spawnMobSafely(Location loc, EntityType type, int maxNearby) {
long count = loc.getWorld().getNearbyEntities(loc, 32, 32, 32).stream()
.filter(e -> e.getType() == type)
.count();
if (count < maxNearby) {
loc.getWorld().spawnEntity(loc, type);
}
}
// 定期清理
Bukkit.getScheduler().runTaskTimer(plugin, () -> {
for (World world : Bukkit.getWorlds()) {
int removed = cleanupEntities(world, EntityType.DROPPED_ITEM, 100);
if (removed > 0) {
plugin.getLogger().info("清理了 " + removed + " 个掉落物");
}
}
}, 6000L, 6000L); // 每 5 分钟
18.3 内存管理
防止内存泄漏
public class PlayerDataManager {
// 不好:玩家退出后数据仍在内存
private final Map<UUID, PlayerData> data = new HashMap<>();
// 好:玩家退出时清理
@EventHandler
public void onQuit(PlayerQuitEvent event) {
UUID uuid = event.getPlayer().getUniqueId();
PlayerData playerData = data.remove(uuid); // 移除并获取
if (playerData != null) {
saveToDatabase(playerData); // 持久化
}
}
}
取消任务
public class MyPlugin extends JavaPlugin {
private final List<BukkitTask> tasks = new ArrayList<>();
@Override
public void onDisable() {
// 取消所有任务
tasks.forEach(BukkitTask::cancel);
tasks.clear();
// 或者一次性取消所有
Bukkit.getScheduler().cancelTasks(this);
}
private void startTask() {
BukkitTask task = Bukkit.getScheduler().runTaskTimer(this, () -> {
// 逻辑
}, 0L, 20L);
tasks.add(task);
}
}
18.4 安全建议
验证用户输入
public class InputValidator {
/**
* 验证玩家名
*/
public static boolean isValidPlayerName(String name) {
return name != null
&& name.length() >= 3
&& name.length() <= 16
&& name.matches("[a-zA-Z0-9_]+");
}
/**
* 验证数值范围
*/
public static boolean isInRange(double value, double min, double max) {
return value >= min && value <= max;
}
/**
* 清理聊天消息(防注入)
*/
public static String sanitize(String input) {
if (input == null) return "";
return input.replaceAll("[§&][0-9a-fk-or]", "") // 移除颜色代码
.trim();
}
/**
* 验证配置文件路径(防路径遍历)
*/
public static boolean isSafePath(String path) {
return path != null
&& !path.contains("..")
&& !path.startsWith("/");
}
}
权限检查
// 始终在执行操作前检查权限
public void handleCommand(CommandSender sender, String[] args) {
// 不好:先执行后检查
// executeAction();
// if (!sender.hasPermission("perm")) { return; }
// 好:先检查后执行
if (!sender.hasPermission("myplugin.admin")) {
sender.sendMessage("§c没有权限!");
return;
}
executeAction();
}
数据保护
// 敏感数据加密
public class DataEncryptor {
private static final String SECRET_KEY = getConfigSecretKey();
public static String encrypt(String data) {
try {
Cipher cipher = Cipher.getInstance("AES");
SecretKeySpec key = new SecretKeySpec(
SECRET_KEY.getBytes(), "AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("加密失败", e);
}
}
public static String decrypt(String encrypted) {
try {
Cipher cipher = Cipher.getInstance("AES");
SecretKeySpec key = new SecretKeySpec(
SECRET_KEY.getBytes(), "AES");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decoded = Base64.getDecoder().decode(encrypted);
return new String(cipher.doFinal(decoded));
} catch (Exception e) {
throw new RuntimeException("解密失败", e);
}
}
}
SQL 注入防护
// 不好:字符串拼接 SQL
String sql = "SELECT * FROM players WHERE name = '" + playerName + "'";
// 好:参数化查询
String sql = "SELECT * FROM players WHERE name = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, playerName);
18.5 兼容性策略
多版本兼容
public class VersionCompat {
private static final int CURRENT_VERSION = getMinecraftVersion();
private static int getMinecraftVersion() {
String version = Bukkit.getBukkitVersion(); // "1.21.4-R0.1-SNAPSHOT"
String[] parts = version.split("-")[0].split("\\.");
return Integer.parseInt(parts[0]) * 100 + Integer.parseInt(parts[1]);
}
/**
* 根据版本使用不同 API
*/
public static void sendActionBar(Player player, String message) {
if (CURRENT_VERSION >= 1210) {
// Paper 1.21+ Adventure API
player.sendActionBar(Component.text(message));
} else {
// 旧版兼容
player.spigot().sendMessage(
ChatMessageType.ACTION_BAR,
TextComponent.fromLegacyText(message)
);
}
}
}
优雅降级
public class FeatureManager {
private boolean papiEnabled = false;
private boolean vaultEnabled = false;
public void initialize(MyPlugin plugin) {
// PlaceholderAPI
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
papiEnabled = true;
new MyPluginExpansion(plugin).register();
plugin.getLogger().info("PlaceholderAPI 已集成");
} else {
plugin.getLogger().info("PlaceholderAPI 未安装,跳过集成");
}
// Vault
if (Bukkit.getPluginManager().getPlugin("Vault") != null) {
vaultEnabled = true;
setupEconomy();
plugin.getLogger().info("Vault 经济已集成");
}
}
public boolean isPAPIEnabled() { return papiEnabled; }
public boolean isVaultEnabled() { return vaultEnabled; }
}
18.6 日志与调试
分级日志
public class PluginLogger {
private final Logger logger;
private boolean debug = false;
public void info(String message) {
logger.info(message);
}
public void warning(String message) {
logger.warning(message);
}
public void severe(String message) {
logger.severe(message);
}
public void debug(String message) {
if (debug) {
logger.info("[DEBUG] " + message);
}
}
public void setDebug(boolean debug) {
this.debug = debug;
}
}
性能监控
public class PerformanceMonitor {
/**
* 测量方法执行时间
*/
public static <T> T measure(String name, Supplier<T> supplier) {
long start = System.nanoTime();
T result = supplier.get();
long elapsed = (System.nanoTime() - start) / 1_000_000; // 毫秒
if (elapsed > 50) { // 超过 50ms 警告
Bukkit.getLogger().warning(String.format(
"[Performance] %s 耗时 %dms", name, elapsed));
}
return result;
}
}
18.7 错误处理
优雅的异常处理
public class SafeExecutor {
/**
* 安全执行任务(捕获异常不影响服务器)
*/
public static void safeRun(Runnable task) {
try {
task.run();
} catch (Exception e) {
Bukkit.getLogger().severe("任务执行失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 带返回值的安全执行
*/
public static <T> Optional<T> safeGet(Supplier<T> supplier) {
try {
return Optional.ofNullable(supplier.get());
} catch (Exception e) {
Bukkit.getLogger().warning("获取数据失败: " + e.getMessage());
return Optional.empty();
}
}
}
用户友好的错误提示
@EventHandler
public void onCommand(PlayerCommandPreprocessEvent event) {
try {
// 处理命令
handleCommand(event);
} catch (Exception e) {
event.setCancelled(true);
event.getPlayer().sendMessage("§c执行命令时出错,请联系管理员。");
plugin.getLogger().severe("命令执行失败: " + e.getMessage());
e.printStackTrace();
}
}
18.8 配置管理
配置热重载
public class ConfigManager {
private FileConfiguration config;
private final JavaPlugin plugin;
public ConfigManager(JavaPlugin plugin) {
this.plugin = plugin;
reload();
}
public void reload() {
plugin.saveDefaultConfig();
plugin.reloadConfig();
config = plugin.getConfig();
}
public String getString(String path, String def) {
return config.getString(path, def);
}
public int getInt(String path, int def) {
return config.getInt(path, def);
}
public boolean getBoolean(String path, boolean def) {
return config.getBoolean(path, def);
}
public List<String> getStringList(String path) {
return config.getStringList(path);
}
}
18.9 更新检查
public class UpdateChecker {
private final JavaPlugin plugin;
private final String resourceId; // SpigotMC 资源 ID
public UpdateChecker(JavaPlugin plugin, String resourceId) {
this.plugin = plugin;
this.resourceId = resourceId;
}
/**
* 异步检查更新
*/
public void check(Consumer<String> callback) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
try {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(
"https://api.spigotmc.org/legacy/resource.php?"
+ resourceId + "&command=getversion"))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
String latest = response.body().trim();
String current = plugin.getDescription().getVersion();
if (!current.equals(latest)) {
Bukkit.getScheduler().runTask(plugin, () ->
callback.accept(latest));
}
} catch (Exception e) {
plugin.getLogger().warning("检查更新失败: " + e.getMessage());
}
});
}
}
18.10 代码审查清单
| 类别 | 检查项 |
|---|
| 功能 | 所有功能是否正常工作 |
| 权限 | 权限检查是否正确 |
| 输入验证 | 用户输入是否已验证 |
| 异常处理 | 异常是否被优雅处理 |
| 性能 | 是否有性能瓶颈 |
| 内存 | 是否有内存泄漏 |
| 线程安全 | 异步操作是否安全 |
| 配置 | 配置是否有默认值 |
| 文档 | 代码是否有注释 |
| 测试 | 关键功能是否有测试 |
18.11 推荐的库
| 库 | 用途 | 说明 |
|---|
| Lombok | 减少样板代码 | @Getter, @Setter |
| HikariCP | 数据库连接池 | 高性能连接池 |
| Jedis | Redis 客户端 | 缓存和发布订阅 |
| ProtocolLib | 数据包操作 | 高级网络功能 |
| Adventure | 文本组件 | 现代化消息系统 |
| CommandAPI | 命令框架 | Brigadier 封装 |
| Configurate | 配置管理 | Paper 原生配置库 |
18.12 常见反模式
| 反模式 | 问题 | 改进 |
|---|
| 主线程 I/O | TPS 下降 | 异步处理 |
| 硬编码配置 | 不灵活 | 使用配置文件 |
| 魔法数字 | 难以维护 | 使用常量 |
| God Class | 过大的类 | 拆分为多个类 |
| 重复代码 | 维护困难 | 提取公共方法 |
| 忽略异常 | 隐藏问题 | 记录日志 |
| 不使用版本控制 | 无法回溯 | 使用 Git |
18.13 扩展阅读
18.14 本章小结
| 要点 | 内容 |
|---|
| 代码规范 | 统一命名、合理结构、设计模式 |
| 性能优化 | 避免高频事件开销、合理缓存、限制实体 |
| 安全 | 验证输入、防注入、权限检查 |
| 兼容性 | 多版本适配、优雅降级 |
| 错误处理 | 捕获异常、记录日志、用户友好提示 |
| 持续改进 | 代码审查、自动化测试、定期重构 |
恭喜你完成了全部 18 章的学习!🎉
回顾学习路线:
入门 → 第 1-5 章(环境 + 基础 API)
进阶 → 第 6-10 章(物品/GUI/世界/实体/计分板)
深入 → 第 11-14 章(数据包/数据库/调度/占位符)
工程 → 第 15-18 章(Docker/测试/发布/最佳实践)
下一步行动建议:
- 🛠️ 动手写一个完整的插件项目
- 📖 阅读 PaperMC 官方文档获取最新 API
- 💬 加入 SpigotMC / PaperMC Discord 社区
- 🧪 为你的项目添加测试
- 🚀 发布到 SpigotMC 或 Hangar
祝你的插件开发之旅顺利!🚀