正在载入

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

编写于

最近更新

文件服务

文件服务 LinFile 统一提供配置文件、语言文件和数据库入口。本文介绍配置文件与语言文件共同使用的对象映射规则;数据库属于另一套数据模型。

ConfigService configService = lin.linFile().config();
LangService langService = lin.linFile().language();
DataService dataService = lin.linFile().database();

配置文件与语言文件都采用代码优先的结构:Java 类声明允许出现的字段、层级和默认值,YAML 或 JSON 文件提供运行时数据。

各项功能的使用方法见:

  1. 配置文件 ⇱:定义、绑定、保存、重载与版本迁移。
  2. 语言文件 ⇱:语言资源与语言对象的管理。
  3. 语言引用 ⇱:Java 中的 LangTextLangListLangMap
  4. 在文件中引用 ⇱:配置中的语言引用语法与回退规则。
  5. 配置文本 ⇱:用公共 API 为插件自己的配置接入语言引用。
  6. 配置校验 ⇱:错误定位、诊断注释与加载失败后的处理。

文件对象

用于绑定配置或语言的 Java 类统称为「文件对象」。配置对象使用 @ConfigFile,语言对象通常使用 @LangPack

@ConfigFile(name = "config")
public class MainConfig {
    public String language = "zh_CN";
    public Database database = new Database();

    public static class Database {
        public String host = "127.0.0.1";
        public int port = 3306;
    }
}

对应的 YAML 为:

language: zh_CN
database:
  host: 127.0.0.1
  port: 3306

文件中的嵌套结构与 Java 对象中的嵌套字段一一对应。内部映射时,Linlang 会把它们视为路径,例如 database.host,再完成默认值合并和字段填充。

约束

文件对象及其嵌套对象应满足以下约束:

  1. 类可以被无参构造。
  2. 参与映射的字段为 public 字段。
  3. 嵌套对象应使用可构造的类,通常声明为 public static class
  4. 字段初始化器只负责提供默认值。
  5. 构造方法和字段初始化器不得依赖尚未建立的运行时状态。

文件对象可以包含供业务代码调用的普通方法,Linlang 不会自动调用它们。

构造实例

首次绑定文件类时,Linlang 会构造一个根对象,先记录它的字段默认值,再将文件内容填入其中。绑定完成后,返回给开发者的就是这个对象,后文称为活动对象。

Linlang 会在绑定开始时记录该实例的独立默认值快照,用于判断和补齐缺失键;随后把磁盘数据填充到同一个实例。默认值快照由文件服务缓存并按只读数据使用,活动对象之后的字段修改不会反向改变默认值。reload() 与缺失补齐会复用这份快照,不会为了重新取得默认值再次构造同类对象。

同一服务重复绑定已经管理的类会复用其根对象。服务重建后再次绑定,则会创建新对象。配置中的动态 Map、List 条目也可能在加载时创建嵌套对象,不能把根对象的复用规则理解成所有构造方法永远只执行一次。

构造方法和字段初始化器仍然会执行,但应保持无副作用,不应执行以下操作:

  1. 读写文件或数据库。
  2. 注册监听器、命令或服务。
  3. 创建线程或计划任务。
  4. 访问尚未初始化的插件单例或 Linlang 服务。
  5. 修改类外部的全局状态。
@ConfigFile(name = "config")
public class MainConfig {
    public String language = "zh_CN";
    public Limits limits = new Limits();

    public MainConfig() {
    }

    public static class Limits {
        public int maxHomes = 3;

        public Limits() {
        }
    }
}

显式声明空的无参构造方法不是必须的。

支持的数据结构

文件字段可以使用:

  1. String、布尔值和数字类型。
  2. Instant 等由映射器明确支持的简单类型。
  3. 嵌套文件对象。
  4. List 与其他集合字段。
  5. Map<String, ?> 形式的映射字段。

集合与 Map 的内容应由 YAML 或 JSON 能直接表达的元素、列表和映射组成。对于具有固定结构的数据,推荐优先使用嵌套类。

public List<String> worlds = new ArrayList<>();
public Map<String, Integer> limits = new LinkedHashMap<>();

请注意,如果您需要读取开放键集合,则应将对应字段声明为 Map,再通过 Map 访问。文件服务不会读取文件中额外添加的键生成 Java 对象。

实现

flowchart TD
    Bind["bind(FileClass)"] --> Metadata["读取文件注解与路径"]
    Metadata --> Construct["无参构造一次活动对象"]
    Construct --> Snapshot["记录默认值快照"]
    Snapshot --> Read["读取 YAML 或 JSON"]
    Read --> Merge["按字段路径合并缺失默认值"]
    Merge --> Populate["填充同一个活动对象"]
    Populate --> Emit{"允许写回?"}
    Emit -->|是| Persist["生成或补齐磁盘文件"]
    Emit -->|否| Return["返回活动对象"]
    Persist --> Return

代码声明配置结构和默认值。默认值用于补齐缺失键,不表示文件中的错误值会被自动替换;配置输入错误的处理见配置校验 ⇱

写回控制

emit 参数控制本次绑定是否允许向磁盘文件写入:

MainConfig config = lin.linFile().config().bind(MainConfig.class, false);
MainLanguage lang = lin.linFile().language().bind(MainLanguage.class, false);

emit = false 表示可以读取并绑定,但不会创建、补齐或保存磁盘文件。也可以在类上声明 @NoEmit 禁止写回;两者任一禁止写回时,服务都不会生成文件。如果不显式传递 false,则 emit = true

如果不希望生成配置文件,可以设置 emit = false

生命周期

同一个文件服务中的 reload() 会重新读取磁盘,并原地刷新已经绑定的活动对象。因此,普通重载后可以继续使用原引用。

lin.settings().apply() 原地应用语言等参数,不替换文件服务或绑定对象。显式重建或关闭后创建新 Facade 时,需要重新取得服务并绑定。

编码

Linlang 和文件服务均使用 Unicode UTF-8 编码。若您在 Windows 下使用 Linlang,请注意文件编码问题。

讨论

请登录账号