序列化自定義資料
如果沒有方法序列化和反序列化,您的資料將不會在重新啟動時持續存在。根據不同的資料型態,Sponge 有幾種不同的方法來對資料進行序列化/反序列化:
DataSerializable 代表一个用于序列化数据的接口,然后你可以使用 DataBuilder 创建和反序列化数据
DataManipulator 类同时实现了
DataSerializable
接口,不过取而代之的是 DataManipulatorBuilder 方法用于创建和反序列化对于那些不能实现
DataSerializable
接口的对象,你可以使用 DataTranslator 用于序列化和反序列化
也就是说,只要实施了注册,事实上所有的 Java 对象都可以定义序列化和反序列化的方式使其和磁盘中的数据相互转换!
讀取 DataViews
每当你读取一个序列化的对象时,一种很吸引人的方式是读取所有你需要的数据以手动创建你的数据对象所需要的对象(及它们相应的参数)。不过,根据存储的数据的内容,有一些方便得多的方式可以帮助你读取数据:
常见的 Java 类型如
int
、String
、double
、List
、和Map
等可以通过内置的方法如getInt(DataQuery)
、getString(DataQuery)
等获取。这些数据组成的列表也可以以类似的方式获取,如getStringList(DataQuery)
等。实现了
DataSerializable
接口的对象可以以getSerializable(DataQuery, Class)
或getSerializableList(DataQuery, Class)
方法获取。除了指定路径外,你还需要指定相应的Class
对象,如Home.class
。一些已经注册相应的
DataTranslator
的对象可以通过getObject(DataQuery, Class)
或getObjectList(DataQuery, Class)
等方法获取。你可以在 DataTranslators 类中找到一个所有默认支持的对象列表。
在上述所有情况下,你都需要指定一个代表路径的 DataQuery 。如果你的数据有相应的 Key
,那么你完全可以通过 key.getQuery()
方法获取指定的路径。此外,你也可以通过调用诸如 DataQuery.of("name")
的方法指定。
小訣竅
你可以指定多层节点对应的路径,如 DataQuery.of("my", "custom", "data")
。
DataBuilder
若欲使一个对象可被序列化,请首先确认它已实现 DataSerializable 接口。你需要且仅需要实现两个方法:
getContentVersion()
方法用于定义当前数据的版本号。toContainer()
方法的返回值将会在反序列化该对象时提供。你可以在返回的DataContainer
中指定任何你想要指定的数据,只要它可以通过上面的若干方法中的一个序列化回来。然后你只要调用set(DataQuery, Object)
方法就可以了。
小訣竅
我们十分建议你使用 Queries.CONTENT_VERSION
路径保存数据的版本号。这将在版本号更新时十分有用,尤其是在应用 DataContentUpdater 的时候。
程式碼範例: 實作 toContainer
import org.spongepowered.api.data.DataContainer;
import org.spongepowered.api.data.DataQuery;
import org.spongepowered.api.data.Queries;
import org.spongepowered.api.data.MemoryDataContainer;
private String name = "Spongie";
@Override
public DataContainer toContainer() {
return DataContainer.createNew()
.set(DataQuery.of("Name"), this.name)
.set(Queries.CONTENT_VERSION, getContentVersion());
}
下一步是实现一个 DataBuilder 。我们十分建议开发者继承 AbstractDataBuilder 类,因为它会在找不到给定版本的数据时尝试将数据更新到最新的版本。你只需要实现一个方法—— build(DataView)
,或是 buildContent(DataView)
——如果你在使用 AbstractDataBuilder
的话。
你应当使用``DataView.contains(Key…)``检查所有你想查询的数据是否存在。若不,则数据很有可能是不完整的,在这种情况下,你应当返回``Optional.empty()``。
若一切正常,则可以使用``getX``方法来创建值并将新对象以``Optional``形式返回。
DataContentUpdater
如果你想在新版本中修改数据存储布局怎么办? DataContentUpdater 就是用于处理这一情况的。如果被序列化的对象小于当前版本,AbstractDataBuilder
将会在数据被传递到构造器之前尝试更新它。
每一个 DataContentUpdater
都需要指定输入版本号和输出版本号。你需要把旧版本的数据传入,同时将其更新到新版本。如果没有办法在更新数据时防止数据损失,你可以在相应的地方提供配置的值——就如同从零开始生成一个数据一样。
最后,你需要确保你的所有 DataContentUpdater
都通过 DataManager#registerContentUpdater()
的方式引用相应的数据类进行了注册——这允许我们在相应的 DataBuilder
中启用它们。
代码示例:实现一个 DataContentUpdater
import org.spongepowered.api.data.persistence.DataContentUpdater
import org.spongepowered.api.text.Text
public class NameUpdater implements DataContentUpdater {
@Override
public int getInputVersion() {
return 1;
}
@Override
public int getOutputVersion() {
return 2;
}
@Override
public DataView update(DataView content) {
String name = content.getString(DataQuery.of("Name")).get();
// For example, version 2 uses a text for the name
return content.set(DataQuery.of("Name"), Text.of(name));
}
}
DataManipulatorBuilder
DataManipualatorBuilder
和 DataBuilder
十分相似,不过它添加了一些与反序列化数据操纵器直接相关的方法。
create()
方法应该返回一个有着默认值的数据操纵器createFrom(DataHolder)
方法和上面的build(DataView)
方法类似,只不过传入的参数变成了 DataHolder 对象。如果相应的数据访问器没有可以获取得到的数据,那么请直接返回create()
方法的返回值。如果该数据操纵器和DataHolder
不兼容,那么你应该返回一个Optional.empty()
。
和 DataBuilder
类似,你需要在相应的 build
方法中返回数据操纵器。
DataManipulatorBuilder
也可以使用 DataContentUpdater ,只要你继承的是 AbstractDataBuilder
类。
你可以使用和注册 DataBuilder
类似的方式注册,只不过你需要使用 register()
方法注册你的 DataManipulatorBuilder
。你必须在其中同时引用可变的和不可变的数据操纵器,当然还有你的 DataManipulatorBuilder
本身。
備註
如果你的插件 API 和实现是分离的,那么你注册的 必须 是实现类。
DataTranslator
很多时候你想要序列化的数据类并没有实现 DataSerializable
接口,如 Vector3d
和 Date
等。如果你想要把这些对象序列化,那么你需要实现一个 DataTranslator 从而 同时 提供序列化和反序列化的方法。
两个 translate
方法和一个 DataSerializable
相应的 toContainer()
以及 build(DataView)
两个方法是等价的,只不过在考虑到数据不存在或者有问题时,你需要抛出一个 InvalidDataException
,而不是返回一个 Optional
。
和其他数据一样,你的 DataTranslator 应在 GameRegistryEvent.Register<DataTranslator<?>>
事件发布时注册。