配置节点
在内存中,配置是以 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
或者Map
java.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
值将一直存在于节点中,我们并不需要显示设置它。