序列化对象

Configurate 库还提供了调整对象的自动序列化和反序列化的方法。默认情况下,一组数据类型可以被序列化和反序列化:其中包括字符串,整数,双精度,UUID,列表(其中的值都是可序列化的)和地图(其中的键值对都是可序列化的)。但是如果你想将你的自定义数据结构写入配置文件,这还远远不够。

想象一个用于跟踪一个玩家已经挖掘了多少钻石的数据结构。它可能看起来有点像这样:

public class DiamondCounter {
    private UUID playerUUID;
    private int diamonds;

    ...
}

此外,我们还假定一些方法用于访问这些字段,一个漂亮的构造函数用于设置,等等。

自定义一个 TypeSerializer

一种非常直接的编写和加载这种数据结构的方法是提供一个自定义的 TypeSerializerTypeSerializer 接口提供了两种方法,一种用于将数据从对象写入配置节点,一种用于从给定的配置节点创建对象。

import com.google.common.reflect.TypeToken;
import ninja.leaping.configurate.objectmapping.ObjectMappingException;
import ninja.leaping.configurate.objectmapping.serialize.TypeSerializer;

public class DiamondCounterSerializer implements TypeSerializer<DiamondCounter> {

    @Override
    public DiamondCounter deserialize(TypeToken<?> type, ConfigurationNode value)
      throws ObjectMappingException {
        UUID player = value.getNode("player").getValue(TypeToken.of(UUID.class));
        int diamonds = value.getNode("diamonds").getInt();
        return new DiamondCounter(player, diamonds);
    }

    @Override
    public void serialize(TypeToken<?> type, DiamondCounter obj, ConfigurationNode value)
      throws ObjectMappingException {
        value.getNode("player").setValue(obj.getPlayerUUID());
        value.getNode("diamonds").setValue(obj.getDiamonds());
    }
}

这一 TypeSerializer 必须通过使用 Configurate 来注册。可以通过全局的注册方式,也就是通过注册到默认的 TypeSerializerCollection ,也可以通过本地的注册方式,也就是在加载配置文件时自定义 ConfigurationOptions 来完成。

代码示例:注册全局的 TypeSerializer

import ninja.leaping.configurate.objectmapping.serialize.TypeSerializers;

TypeSerializers.getDefaultSerializers().registerType(TypeToken.of(DiamondCounter.class), new DiamondCounterSerializer());

代码示例:注册本地的 TypeSerializer

import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.ConfigurationOptions;
import ninja.leaping.configurate.objectmapping.serialize.TypeSerializerCollection;
import ninja.leaping.configurate.objectmapping.serialize.TypeSerializers;

TypeSerializerCollection serializers = TypeSerializers.getDefaultSerializers().newChild();
serializers.registerType(TypeToken.of(DiamondCounter.class), new DiamondCounterSerializer());
ConfigurationOptions options = ConfigurationOptions.defaults().setSerializers(serializers);
ConfigurationNode rootNode = someConfigurationLoader.load(options);

警告

如果你为在你自己的插件中没有引入的类型提供了一个自定义的 TypeSerializer ,那么你应该只在本地注册它们,以避免因为 TypeSerializer 被覆盖,而引起的与其他插件或 Sponge 的冲突。

使用 ObjectMapper

因为在许多情况下,序列化和反序列化都可以归结为映射字段到配置节点,编写这样的 TypeSerializer 是一个相当沉闷无聊的事情,因此我们希望 Configurate 自己做。所以让我们使用 ConfigSerializableSetting 两个注解,来注解我们的类。

import ninja.leaping.configurate.objectmapping.Setting;
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;

@ConfigSerializable
public class DiamondCounter {

    @Setting(value="player", comment="Player UUID")
    private UUID playerUUID;
    @Setting(comment="Number of diamonds mined")
    private int diamonds;

    ...
}

以上示例现在可以从配置节点序列化和反序列化,而无需进一步注册。 @Setting 这一注解将配置节点映射到已被注解的字段。 它接受两个可选参数, valuecomment 。如果 value 参数存在,它定义了用于保存字段的节点的名称。如果不存在,将使用字段的名称。所以在上面的例子中,字段的注解确保字段 playerUUID 的内容保存到节点“player”,并使用“Player UUID”注释。不过,“diamonds”字段将被直接保存在“diamonds”名称下,因为它的注解仅仅指定了注释。如果实现的配置节点支持注释,那么该注释将写入配置,否则将被丢弃。

小訣竅

你也可以使用简写的 @Setting("someNode") 而不是 @Setting(value="someNode")

通过 @ConfigSerializable ,我们不需要任何的注册,因为它允许 Configurate 为被注解的类生成一个对应的 ObjectMapper 。唯一的限制是 Configurate 需要一个空的构造函数来实例化一个新的对象,然后填充被注解的字段。

提供自定义的 ObjectMapperFactory

然而,如果我们使用不同的 ObjectMapperFactory ,例如 GuiceObjectMapperFactory ,那么这个限制可以被解除。它不需要一个空的构造函数,因此可以工作在任何类,因为 Guice 可以通过依赖注入创建。这也允许混合使用 @Inject@Setting 用于注解字段。

你的插件只需通过依赖注入(参见 相依注入 )获取一个 GuiceObjectMapperFactory ,然后将其传递给 ConfigurationOptions

import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.state.GamePreInitializationEvent;
import org.spongepowered.api.plugin.Plugin;
import com.google.inject.Inject;
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import ninja.leaping.configurate.objectmapping.GuiceObjectMapperFactory;

@Plugin(name="IStoleThisFromZml", id="shamelesslystolen", version="0.8.15")
public class StolenCodeExample {

    @Inject private GuiceObjectMapperFactory factory;
    @Inject private ConfigurationLoader<CommentedConfigurationNode> loader;

    @Listener
    public void enable(GamePreInitializationEvent event) {
        CommentedConfigurationNode node =
          loader.load(ConfigurationOptions.defaults().setObjectMapperFactory(factory));
        DiamondCounter myDiamonds = node.getValue(TypeToken.of(DiamondCounter.class));
    }
}

備註

上面的代码只是一个例子。为了简洁,上面的代码缺少适当的异常处理。