强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

PaperMC 插件开发完全指南 / 第 18 章:最佳实践

第 18 章:最佳实践

汇集代码规范、性能优化、安全建议和兼容性策略,帮助你写出高质量的插件。


18.1 代码规范

命名规范

类型规范示例
包名全小写,逆域名com.example.myplugin
类名PascalCasePlayerManager, EconomyService
方法名camelCasegetBalance(), sendTitle()
常量UPPER_SNAKE_CASEMAX_PLAYERS, DEFAULT_LANG
变量camelCaseplayerCount, isReady
配置键snake_casedatabase.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 / HashSetArrayList.contains()
有序数据TreeMap / TreeSet手动排序
只读列表List.of() / Set.of()Collections.unmodifiableXxx()
大量数值原生数组 (int[])ArrayList<Integer>
并发访问ConcurrentHashMapsynchronized 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数据库连接池高性能连接池
JedisRedis 客户端缓存和发布订阅
ProtocolLib数据包操作高级网络功能
Adventure文本组件现代化消息系统
CommandAPI命令框架Brigadier 封装
Configurate配置管理Paper 原生配置库

18.12 常见反模式

反模式问题改进
主线程 I/OTPS 下降异步处理
硬编码配置不灵活使用配置文件
魔法数字难以维护使用常量
God Class过大的类拆分为多个类
重复代码维护困难提取公共方法
忽略异常隐藏问题记录日志
不使用版本控制无法回溯使用 Git

18.13 扩展阅读


18.14 本章小结

要点内容
代码规范统一命名、合理结构、设计模式
性能优化避免高频事件开销、合理缓存、限制实体
安全验证输入、防注入、权限检查
兼容性多版本适配、优雅降级
错误处理捕获异常、记录日志、用户友好提示
持续改进代码审查、自动化测试、定期重构

恭喜你完成了全部 18 章的学习!🎉

回顾学习路线:

入门 → 第 1-5 章(环境 + 基础 API)
进阶 → 第 6-10 章(物品/GUI/世界/实体/计分板)
深入 → 第 11-14 章(数据包/数据库/调度/占位符)
工程 → 第 15-18 章(Docker/测试/发布/最佳实践)

下一步行动建议:

  1. 🛠️ 动手写一个完整的插件项目
  2. 📖 阅读 PaperMC 官方文档获取最新 API
  3. 💬 加入 SpigotMC / PaperMC Discord 社区
  4. 🧪 为你的项目添加测试
  5. 🚀 发布到 SpigotMC 或 Hangar

祝你的插件开发之旅顺利!🚀