配置节点
在内存中,配置是以 ConfigurationNode 的形式存储起来的。一个 ConfigurationNode 不仅存储一份数据(比如一个数,一个字符串,或者一个列表),而且还可以存储一个子节点,也就是说以树的形式存储。当使用 ConfigurationLoader 加载或者新建一个配置的时候,它会返回配置数据的 根节点 。我们十分建议你始终保存一个根节点的引用。
備註
通过对 ConfigurationLoader 的使用,你甚至可以获取到一个 CommentedConfigurationNode 。除了正常的 ConfigurationNode 提供的行为之外, CommentedConfigurationNode 还能够保留配置文件中保存的注释。
数据值
基本值
一些基本的值诸如 int 、 double 、 boolean 、或者 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 使用的,依赖于文件格式的底层代码处理掉了。不过,一些原生的数据类型,诸如 String 、 List 、和 Map 等,一般情况下也是可以处理的。
序列化和反序列化
如果你想要读取或者存储一个并不属于上面提到的数据类型的对象,你需要首先将其反序列化。在用于创建你的 ConfigurationNode 的 ConfigurationOptions 中,有一系列的 TypeSerializer 可以被 Configurate 用于把你的对象序列化成一个 ConfigurationNode ,反之亦然。
为了告诉 Configurate 你需要处理的是什么数据类型,我们需要提供一个 Guava 的 TypeToken 。现在想像我们需要通过 towns.aFLARDia.mayor 对应的节点读取玩家的 UUID 。为了达到目的,我们需要通过调用 getValue() 方法,并传入一个代表 UUID 的 TypeToken 。
import java.util.UUID;
UUID mayor = rootNode.getNode("towns", "aFLARDia", "mayor").get(TypeToken.of(UUID.class));
这提醒 Configurate 查找适用于 UUID 的 TypeSerializer 以将存储的值转化为 UUID 。如果 TypeSerializer (乃至上面的方法)获取到了不完整或者不正确的数据,它可能会抛出一个 ObjectMappingException 。
用于把一个新的 UUID 写入该配置节点的代码格式十分相似。只需要通过使用 setValue() 方法,并传入一个 TypeToken 和你想要序列化的对象。
rootNode.getNode("towns","aFLARDia", "mayor").setValue(TypeToken.of(UUID.class), newUuid);
備註
如果找不到适用于给定 TypeToken 的 TypeSerializer ,执行序列化数据的代码就会抛出一个 ObjectMappingException 。
对于像 UUID 这样的简单类,你只需要通过 TypeToken.of() 这一静态方法创建一个 TypeToken 。不过当你想要使用带有泛型的类型(诸如 Map<String,UUID> )时,代码格式会稍稍麻烦一点。在大多数情况下你只需要通过匿名内部类的方式继承这一 TypeToken : new TypeToken<Map<String,UUID>>() {} 就可以了。这样在编译后的代码中仍然会保存相应的泛型,因此即使是带有泛型,我们仍然可以很方便地存储和读取数据。
也參考
关于 TypeToken 的更多信息,请参阅 Guava 官方文档 。
使用这些方法可以序列化的类型有:
所有基本数据类型(见上)
所有包含可序列化数据的
List或者Mapjava.util.UUID、java.net.URL、java.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 值将一直存在于节点中,我们并不需要显示设置它。