正在载入

等待时间过长时请刷新页面

编写于

最近更新

重载机制

从 2.6.0.0 开始,Linlang 的日常重载不再重建 Facade 或命令服务。命令、文件绑定、消息服务以及 GUI 的 Source 和 Hook 注册都可以继续使用。

重载指的是更新当前运行时中插件的数据,并不会调用插件的 onEnable。

请勿使用任何热重载服务重载 Linlang 或依赖 Linlang 的插件

选择操作

目的调用
从磁盘重读配置和语言,并刷新业务缓存与 GUIlin.reload()
改变前缀或日志方式lin.settings()...apply()
切换当前语言lin.settings().totalLocale("en_GB").apply()
只重载语言文件lin.linFile().language().reload()
只重载某个界面定义lin.linView().reload("sessionID")

settings 中的设置先保存在设置对象里,调用 apply 后才提交。修改前缀不会顺带重读配置,因此不会覆盖尚未保存的内存修改。切换语言只处理语言及其关联显示,不需要再次绑定配置对象或注册命令。

完整重载

调用 lin.reload() 后,当前插件的 Facade 按以下顺序处理:

  1. 读取审计设置。
  2. 重载已绑定的配置。
  3. 重载当前语言,或应用已经暂存的语言参数。
  4. 重新计算动态前缀。
  5. 执行插件注册的重载回调。
  6. 重载 GUI 定义,刷新活动会话。

文件步骤出现错误后,仍会尝试其他文件步骤,但不会继续执行业务回调和 GUI 定义重载。语言服务自身的变更通知可能已经刷新界面文本;这与重新加载 GUI 定义、重新执行 Source 是两件事。

更新业务缓存

配置字段更新后,插件自己计算的缓存不会自动更新。例如根据配置构造的奖励表、菜单索引或规则集合,需要插件重新生成。

在初始化时注册一次回调:

lin.onReload("reward-cache", () -> {
    var next = buildRewardCache(config);
    validateRewardCache(next);
    rewardCache = next;
});

同名回调会替换旧回调,传入 null 可以移除。回调按注册顺序执行;一个回调失败,其他回调仍会被尝试,但本轮不会继续重载 GUI 定义。

如果语言由插件配置决定,可以先注册语言回调:

lin.onReload("language", () ->
        lin.settings().totalLocale(config.language).apply());

回调中不要再次调用 reload、restart 或 close,也不要重复注册命令。构造新缓存时应使用临时对象,验证通过后再替换旧值;Linlang 无法撤销业务代码已经产生的副作用。

出错时保留什么

语言文件会先完成解析和字段校验,再更新绑定对象。普通语言重载以语言包为单位提交:某个包出错时,该包保留旧字段和语言引用快照,其他有效包可以更新。出错包暂停保存,避免用旧内容覆盖需要修复的文件;修复并成功加载后恢复保存。

切换语言时,所有已绑定语言包先完成准备,再提交新的 locale。准备失败就保留原语言。监听器是在内容提交后执行的,因此监听器失败会报告错误,但不会撤回已经有效提交的语言文本。

GUI 的新定义先编译,再准备各个查看者的新会话。定义或 Source 加载失败不会预先清空旧会话。平台窗口已经发生变化后再失败时,会尽力恢复旧窗口;恢复本身也失败的原因会保留在异常中。

这些保护不是全局事务。多个配置文件之间、文件写入之间,以及业务回调之间,都不保证一起成功或一起回滚。

异常与线程

部分步骤失败时,reload 会抛出 ReloadException。它的 failures() 保存失败步骤及原因,对应的详细问题已经由审计服务记录。命令处理代码应只发送简短的失败提示,不要再次打印整份堆栈,也不能继续发送重载成功。

Bukkit 中的生命周期操作和语言重载必须在主线程执行。递归重载、重载期间关闭服务,以及继续重载已关闭的 Facade 都会被拒绝。不要把整个重载过程直接放进异步任务。

显式重建

lin.restart() 与 reload 不同:它关闭旧命令、消息和 GUI 服务,创建新的配置、语言、命令、消息和 GUI 服务,然后执行插件的初始化回调。

Facade 入口保持不变,但旧的服务实例、文件绑定和语言引用不可继续用于新服务。活动 GUI 会话关闭,Source、Hook、消息传输器和命令需要重新注册。数据库连接和审计资源保留,不会随这次重建中断。

Linlang 无法自行恢复插件的业务注册,因此必须先注册重建回调:

private void installServices() {
    config = lin.linFile().config().bind(MainConfig.class);
    lang = lin.linFile().language().bind(MainLang.class);
    lin.settings()
            .totalLocale(config.language)
            .dynamicTotalPrefix(context -> lang.prefix.resolve())
            .apply();
    registerCommands();
    registerViews();
    registerReloadCallbacks();
}

初始化时,先取得 lin,再安装服务和回调:

lin = Lin.init(this);
installServices();
lin.onRebuild(this::installServices);

这里只是提取插件已有的初始化代码,不要在 installServices 中再次调用 Lin.init,也不要重复注册 Bukkit 监听器或调度任务。插件自己持有的资源仍由插件负责管理。

重建回调只有一个,重复注册会替换,传入 null 可以移除。普通重载不会调用它。重建时旧的 onReload 回调会清除,因此应在初始化方法里一并注册新的回调,避免它们捕获旧文件对象。

未注册 onRebuild,或同一个插件存在多个 Facade 时,会在拆除旧服务前拒绝重建。重载和重建期间均禁止再次 reload、restart 或 close。

新服务开始替换后,如果关闭旧资源、创建新服务或执行回调失败,Linlang 会关闭该 Facade 并汇总报告错误,避免继续提供半初始化的服务。这不是可回滚的重建;插件需要修复初始化问题并重新启用,不能继续持有已关闭的入口。

运行时保留以下管理命令:

命令行为
/linlang reload <插件名称>只重载指定插件的 Facade,保留服务实例
/linlang reload-all重载运行时和所有已注册插件,保留服务实例
/linlang restart <插件名称>重建指定插件的门面服务
/linlang restart-all逐个尝试重建全部插件,报告成功数量
/linlang files repair <插件名称>重新读取并修复指定已注册插件文件中的缺失键
/linlang files repair-all修复全部已注册插件文件中的缺失键,并列出实际发生修复的插件及键数

reload 必须指定插件名称,不带名称不会执行全部重载。插件匹配优先使用完整名称,不区分大小写;只有一个部分匹配结果时也可执行,存在多个候选时会提示进一步明确名称。reload、restart 和 files repair 都根据当前已注册插件提供 Tab 补全。文件修复命令不会把 Linlang 运行时插件自身列为目标。

这些命令不会重新执行 Bukkit 插件的 onEnable。共享运行时入口也不允许直接调用 restart,避免误拆除运行时本身。

从旧版本迁移

将日常设置统一改为 settings。parameters 仅作为已弃用的兼容入口保留;设置和语言切换都不触发重建。

删除 reload 后重复绑定文件、重复注册命令的代码。只有显式 restart 时,才通过 onRebuild 重新取得服务并完成注册。

讨论

请登录账号