配置节点

在内存中,配置是以 ConfigurationNode 的形式存储起来的。一个 ConfigurationNode 不仅存储一份数据(比如一个数,一个字符串,或者一个列表),而且还可以存储一个子节点,也就是说以树的形式存储。当使用 ConfigurationLoader 加载或者新建一个配置的时候,它会返回配置数据的 根节点 。我们十分建议你始终保存一个根节点的引用。

注解

通过对 ConfigurationLoader 的使用,你甚至可以获取到一个 CommentedConfigurationNode 。除了正常的 ConfigurationNode 提供的行为之外, CommentedConfigurationNode 还能够保留配置文件中保存的注释。

数据值

基本值

一些基本的值诸如 intdoubleboolean 、或者 String 每个都有对应的方便的 Getter ,在该节点不包含对应类型的值时返回默认值。让我们检查我们的服务器管理员是否想要我们的插件允许启用 blockCheats 模块,也就是检查 modules.blockCheats.enabled 中配置的值。

boolean shouldEnable = rootNode.getNode("modules", "blockCheats", "enabled").getBoolean();

是的,它就是这么简单。和上面的示例类似,一些诸如 ConfigurationNode#getInt()ConfigurationNode#getDouble() 、或者 ConfigurationNode#getString() 等方法同样可以用于方便地获取想要的数据类型。

如果想要设置一个节点为基本的值,只需要使用 ConfigurationNode#setValue(Object) 方法。你根本不需要担心它传入了一个 Object 会带来什么——其实它只是意味着可以传入任何东西,然后配置系统会自行决定如何处理并存储它。

想象一下 blockCheats 模块可以通过用户命令停用。你可以通过下面的代码将其反映在配置中:

rootNode.getNode("modules", "blockCheats", "enabled").setValue(false);

警告

所有不属于基本类型的数据都不能通过上面那些基本的方法去完成,也就是必须通过下面的示例中所展示的(反)序列化方法以读写它。基本类型的数据都已被原生地通过 ConfigurationLoader 使用的,依赖于文件格式的底层代码处理掉了。不过,一些原生的数据类型,诸如 StringList 、和 Map 等,一般情况下也是可以处理的。

序列化和反序列化

如果你想要读取或者存储一个并不属于上面提到的数据类型的对象,你需要首先将其反序列化。在用于创建你的 ConfigurationNodeConfigurationOptions 中,有一系列的 TypeSerializer 可以被 Configurate 用于把你的对象序列化成一个 ConfigurationNode ,反之亦然。

为了告诉 Configurate 你需要处理的是什么数据类型,我们需要提供一个 Guava 的 TypeToken 。现在想像我们需要通过 towns.aFLARDia.mayor 对应的节点读取玩家的 UUID 。为了达到目的,我们需要通过调用 getValue() 方法,并传入一个代表 UUIDTypeToken

import java.util.UUID;

UUID mayor = rootNode.getNode("towns", "aFLARDia", "mayor").get(TypeToken.of(UUID.class));

这提醒 Configurate 查找适用于 UUIDTypeSerializer 以将存储的值转化为 UUID 。如果 TypeSerializer (乃至上面的方法)获取到了不完整或者不正确的数据,它可能会抛出一个 ObjectMappingException

用于把一个新的 UUID 写入该配置节点的代码格式十分相似。只需要通过使用 setValue() 方法,并传入一个 TypeToken 和你想要序列化的对象。

rootNode.getNode("towns","aFLARDia", "mayor").setValue(TypeToken.of(UUID.class), newUuid);

注解

如果找不到适用于给定 TypeTokenTypeSerializer ,执行序列化数据的代码就会抛出一个 ObjectMappingException

对于像 UUID 这样的简单类,你只需要通过 TypeToken.of() 这一静态方法创建一个 TypeToken 。不过当你想要使用带有泛型的类型(诸如 Map<String,UUID> )时,代码格式会稍稍麻烦一点。在大多数情况下你只需要通过匿名内部类的方式继承这一 TypeTokennew TypeToken<Map<String,UUID>>() {} 就可以了。这样在编译后的代码中仍然会保存相应的泛型,因此即使是带有泛型,我们仍然可以很方便地存储和读取数据。

参见

关于 TypeToken 的更多信息,请参阅 Guava 官方文档

使用这些方法可以序列化的类型有:

  • 所有基本数据类型(见上)

  • 所有包含可序列化数据的 List 或者 Map

  • java.util.UUIDjava.net.URLjava.net.URI 、以及 java.util.regex.Pattern

  • 页面 序列化对象 列出了所有可以序列化的数据类型

默认值

和 Sponge API 不同,第三方库 Configurate 并不使用 Optional 以表示可能返回 null 的数据。尽管用于原始数据类型的 Getter 方法(诸如 getBoolean()getInt() )会返回诸如 false 或者 0 这样的值,但一些会返回对象的方法(诸如 getString() )可能会在不存在值时返回 null 。如果你并不想手动处理这些特殊的情况,你可以使用 默认值 以解决问题。上面提到的所有诸如 getXXX() 的方法,都分别有一个重载的方法用于传入额外的作为默认值的参数。

让我们再一次看一看读取布尔值的代码示例。

boolean shouldEnable = rootNode.getNode("modules", "blockCheats", "enabled").getBoolean();

这一方法在对应值不存在或者对应值被设置为 false 时都将返回 false 。由于我们根本没有办法分辨这两个情况,因此如果我们想要把配置文件中的默认值指定为 false ,我们也没有什么更简单的方法了。除非我们想要指定默认值为 true

boolean shouldEnable = rootNode.getNode("modules", "blockCheats", "enabled").getBoolean(true);

你同样可以指定你从配置中获取到的任何数据的默认值,因此避免了返回一个 null ,或者抛出一个 ObjectMappingException ,从而导致数据的缺失。当然你在反序列化 getValue() 方法时也可以使用默认值,下面就是一些例子:

String greeting = rootNode.getNode("messages", "greeting").getString("FLARD be with you good man!");

UUID mayor = rootNode.getNode("towns", "aFLARDia", "mayor")
                        .getValue(TypeToken.of(UUID.class), somePlayer.getUniqueId());

另一个用途比较大的应用是把默认值复制到你的配置中,如果需要的话。在创建你的根节点时,你可以传入调用了 setShouldCopyDefaults(true) 方法的 ConfigurationOptions 。随后你只要提供了一个默认值, Configurate 将首先检查值是否存在,如果不存在,那么它将会在使用默认值返回之前保存这一默认值到你的配置当中。

让我们假设你的插件是第一次运行,也就是配置文件并不存在。你试着通过设置 ConfigurationOptions 以在加载时允许复制默认值,然后你就获取到了一个空的配置节点。当你执行代码 rootNode.getNode("modules", "blockCheats", "enabled").getBoolean(true) 时,因为节点并不存在, Configurate 便自动创建了这么一个节点,并根据之前 ConfigurationOptions 决定的方式,写入一个 true 。这一 true 值将一直存在于节点中,我们并不需要显示设置它。