序列化自定义数据

如果没有相应的方法用于序列化和反序列化,那么你的数据并不会在服务端重启后仍然存在。Sponge 根据不同的数据类型提供了若干种不同的方式用于序列化和反序列化数据:

  • DataSerializable 代表一个用于序列化数据的接口,然后你可以使用 DataBuilder 创建和反序列化数据
  • DataManipulator 类同时实现了 DataSerializable 接口,不过取而代之的是 DataManipulatorBuilder 方法用于创建和反序列化
  • 对于那些不能实现 DataSerializable 接口的对象,你可以使用 DataTranslator 用于序列化和反序列化

也就是说,只要实施了注册,事实上所有的 Java 对象都可以定义序列化和反序列化的方式使其和磁盘中的数据相互转换!

读取 DataView

每当你读取一个序列化的对象时,一种很吸引人的方式是读取所有你需要的数据以手动创建你的数据对象所需要的对象(及它们相应的参数)。不过,根据存储的数据的内容,有一些方便得多的方式可以帮助你读取数据:

  • 常见的 Java 类型如 intStringdoubleList 、和 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``形式返回。

最后,你需要注册这个构造器好使插件找到它。只要调用``DataManager#registerDataBuilder(Class, DataBuilder)``并传入数据类类型和构造器实例即可。

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 类

DataManipualatorBuilderDataBuilder 十分相似,不过它添加了一些与反序列化数据操纵器直接相关的方法。

  • create() 方法应该返回一个有着默认值的数据操纵器
  • createFrom(DataHolder) 方法和上面的 build(DataView) 方法类似,只不过传入的参数变成了 DataHolder 对象。如果相应的数据访问器没有可以获取得到的数据,那么请直接返回 create() 方法的返回值。如果该数据操纵器和 DataHolder 不兼容,那么你应该返回一个 Optional.empty()

DataBuilder 类似,你需要在相应的 build 方法中返回数据操纵器。

DataManipulatorBuilder 也可以使用 DataContentUpdater 类 ,只要你继承的是 AbstractDataBuilder 类。

你可以使用和注册 DataBuilder 类似的方式注册,只不过你需要使用 register() 方法注册你的 DataManipulatorBuilder 。你必须在其中同时引用可变的和不可变的数据操纵器,当然还有你的 DataManipulatorBuilder 本身。

注解

如果你的插件 API 和实现是分离的,那么你注册的 必须 是实现类。

DataTranslator 类

很多时候你想要序列化的数据类并没有实现 DataSerializable 接口,如 Vector3dDate 等。如果你想要把这些对象序列化,那么你需要实现一个 DataTranslator 从而 同时 提供序列化和反序列化的方法。

两个 translate 方法和一个 DataSerializable 相应的 toContainer() 以及 build(DataView) 两个方法是等价的,只不过在考虑到数据不存在或者有问题时,你需要抛出一个 InvalidDataException ,而不是返回一个 Optional

当然,和其他数据类一样,你需要用 DataManager#registerTranslator(Class, DataTranslator) 方法注册你的 DataTranslator 类。