编解码器
概述
在本指南中,您可以了解有关 编解码器 以及在 MongoDB Kotlin 驱动程序中处理 Kotlin 对象的编码和解码的辅助类的信息。以下Codec 抽象允许您将任何 Kotlin 类型映射到相应的 BSON 类型。您可以使用此功能将您的域对象直接映射到和从 BSON,而不是使用数据类或中间基于映射的对象,如 Document 或 BsonDocument。
您可以在以下部分了解如何使用 Codec 抽象指定自定义编码和解码逻辑,并查看示例实现。
编解码器
Codec 接口包含用于将 Kotlin 对象序列化和反序列化为 BSON 数据的抽象方法。您可以在实现此接口时定义您在 BSON 与您的 Kotlin 对象之间的转换逻辑。
要实现 Codec 接口,请重写 encode()、decode() 和 getEncoderClass() 抽象方法。
encode() 方法需要以下参数
参数类型 | 描述 |
|---|---|
writer | BsonWriter 接口的实现实例的一个实例,该接口公开了用于写入 BSON 文档的方法。例如,BsonBinaryWriter 实现将数据写入到二进制数据流中。使用此实例使用适当的写入方法写入您的 BSON 值。 |
value | 您的实现编码的数据。类型必须与分配给您的实现的类型变量匹配。 |
encoderContext | 包含有关它编码到 BSON 的 Kotlin 对象数据的元信息,包括是否将当前值存储在 MongoDB 集合中。 |
此方法使用 BsonWriter 实例将编码的值发送到 MongoDB,并且不返回任何值。
decode() 方法返回一个填充了BSON数据值的Kotlin对象实例。此方法需要以下参数
参数类型 | 描述 |
|---|---|
bsonReader | 一个实现了 BsonReader 接口的类的实例,该接口公开了读取BSON文档的方法。例如,BsonBinaryReader 实现从数据流中读取。 |
decoderContext | 包含解码到Kotlin对象中的BSON数据的信息。 |
getEncoderClass() 方法返回Kotlin类的类实例,因为Kotlin无法推断类型,这是由于类型擦除。
请参阅以下代码示例,说明如何实现自定义的 Codec。
PowerStatus 枚举包含 "ON" 和 "OFF" 值,用于表示电开关的状态。
enum class PowerStatus { ON, OFF }
PowerStatusCodec 类实现 Codec,以便将Kotlin enum 值转换为相应的BSON布尔值。 encode() 方法将 PowerStatus 转换为BSON布尔值,而 decode() 方法则执行相反的转换。
class PowerStatusCodec : Codec<PowerStatus> { override fun encode(writer: BsonWriter, value: PowerStatus, encoderContext: EncoderContext) = writer.writeBoolean(value == PowerStatus.ON) override fun decode(reader: BsonReader, decoderContext: DecoderContext): PowerStatus { return when (reader.readBoolean()) { true -> PowerStatus.ON false -> PowerStatus.OFF } } override fun getEncoderClass(): Class<PowerStatus> = PowerStatus::class.java }
您可以将 PowerStatusCodec 的实例添加到您的 CodecRegistry 中,该 CodecRegistry 包含您的 Codec 与其应用的Kotlin对象类型之间的映射。有关如何包含您的 Codec 的信息,请参阅此页面的 CodecRegistry 部分。
有关本节中类和接口的更多信息,请参阅以下API文档
CodecRegistry
CodecRegistry 是一个不可变的 Codec 实例集合,它们可以编码和解码它们指定的Kotlin类。您可以使用以下 CodecRegistries 类静态工厂方法从关联类型中构建 CodecRegistry:
fromCodecs()fromProviders()fromRegistries()
以下代码片段显示了如何使用 fromCodecs() 方法构建 CodecRegistry:
val codecRegistry = CodecRegistries.fromCodecs(IntegerCodec(), PowerStatusCodec())
在上面的示例中,我们为 CodecRegistry 分配以下 Codec 实现:
IntegerCodec,一个将Integers转换的Codec,是 BSON 包的一部分。PowerStatusCodec,我们的示例
Codec,它将 Kotlin 枚举值转换为 BSON 布尔值。
您可以使用以下代码从上一个示例中的 CodecRegistry 实例检索 Codec 实例:
val powerStatusCodec = codecRegistry.get(PowerStatus::class.java) val integerCodec = codecRegistry.get(Integer::class.java)
如果您尝试检索未注册的类的 Codec 实例,则 get() 方法将抛出 CodecConfigurationException 异常。
有关本节中类和接口的更多信息,请参阅以下API文档
CodecProvider
CodecProvider 是一个接口,包含创建 Codec 实例并将其分配给 CodecRegistry 实例的抽象方法。类似于 CodecRegistry,BSON 库使用 get() 方法检索到的 Codec 实例在 Kotlin 和 BSON 数据类型之间进行转换。
然而,在您添加包含需要相应 Codec 对象的字段的类的情况下,您需要确保在实例化类的 Codec 之前,实例化类的字段上的 Codec 对象。您可以使用 get() 方法中的 CodecRegistry 参数传递 Codec 依赖的任何 Codec 实例。
以下代码示例展示了如何实现 CodecProvider,以便在 CodecRegistry 实例(如我们之前的示例中的 PowerStatusCodec)中传递 MonolightCodec 所需要的任何 Codec 实例。
class MonolightCodec(registry: CodecRegistry) : Codec<Monolight> { private val powerStatusCodec: Codec<PowerStatus> private val integerCodec: Codec<Int> init { powerStatusCodec = registry[PowerStatus::class.java] integerCodec = IntegerCodec() } override fun encode(writer: BsonWriter, value: Monolight, encoderContext: EncoderContext) { writer.writeStartDocument() writer.writeName("powerStatus") powerStatusCodec.encode(writer, value.powerStatus, encoderContext) writer.writeName("colorTemperature") integerCodec.encode(writer, value.colorTemperature, encoderContext) writer.writeEndDocument() } override fun decode(reader: BsonReader, decoderContext: DecoderContext): Monolight { val monolight = Monolight() reader.readStartDocument() while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { when (reader.readName()) { "powerStatus" -> monolight.powerStatus = powerStatusCodec.decode(reader, decoderContext) "colorTemperature" -> monolight.colorTemperature = integerCodec.decode(reader, decoderContext) "_id" -> reader.readObjectId() } } reader.readEndDocument() return monolight } override fun getEncoderClass(): Class<Monolight> = Monolight::class.java }
要查看使用这些 Codec 类进行读写操作的示例,请参阅本指南的 自定义 Codec 示例 部分。
默认编解码器注册表
默认编解码器注册表是一组指定常用 Kotlin 和 MongoDB 类型之间转换的 CodecProvider 类。驱动程序默认使用默认编解码器注册表,除非您指定了不同的注册表。
如果您需要覆盖一个或多个 Codec 类的行为,但保留默认编解码器注册表中的其他类的行为,您可以按优先级顺序指定所有注册表。例如,假设您想覆盖默认提供程序对枚举类型的 Codec 行为,使用您的自定义 MyEnumCodec,您必须将其添加到注册表列表中,如以下示例所示
val newRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(MyEnumCodec()), MongoClientSettings.getDefaultCodecRegistry() )
有关本节中类和接口的更多信息,请参阅以下 API 文档部分
BsonTypeClassMap
BsonTypeClassMap 类包含 BSON 和 Kotlin 类型之间的推荐映射。您可以在自定义 Codec 或 CodecProvider 中使用此类来帮助您管理哪些 Kotlin 类型应将您的 BSON 类型解码为实现 Iterable 或 Map 的容器类,例如 Document 类。
您可以通过传递包含新条目或替换条目的 Map 来添加或修改 BsonTypeClassMap 的默认映射。
以下代码片段显示了如何检索默认 BsonTypeClassMap 实例中与 BSON 类型相对应的 Kotlin 类类型
val bsonTypeClassMap = BsonTypeClassMap() val clazz = bsonTypeClassMap[BsonType.ARRAY] println("Class name: " + clazz.name)
Java type: java.util.List
您可以通过指定替换来修改实例中的这些映射,在 BsonTypeClassMap 构造函数中。以下代码片段展示了如何将您的 BsonTypeClassMap 实例中的 ARRAY 映射替换为 Set 类
val replacements = mutableMapOf<BsonType, Class<*>>(BsonType.ARRAY to MutableSet::class.java) val bsonTypeClassMap = BsonTypeClassMap(replacements) val clazz = bsonTypeClassMap[BsonType.ARRAY] println("Class name: " + clazz.name)
Java type: java.util.Set
有关默认映射的完整列表,请参阅BsonTypeClassMap API 文档。
有关 Document 类如何使用 BsonTypeClassMap 的示例,请参阅以下类的驱动程序源代码
自定义编解码器示例
在本节中,我们展示了如何实现 Codec 和 CodecProvider 以定义自定义 Kotlin 类的编码和解码逻辑。我们还展示了如何指定和使用您的自定义实现以执行插入和检索操作。
提示
Kotlin 序列化
作为实现自定义编解码器的替代方案,您可以使用 Kotlin 序列化来处理您的数据编码和解码,使用 @Serializable 类。如果您已经熟悉该框架或更喜欢使用 Kotlin 的惯用方法,您可能会选择 Kotlin 序列化。有关更多信息,请参阅Kotlin 序列化 文档。
以下代码片段展示了我们名为 Monolight 的示例自定义类及其我们想要存储和从 MongoDB 集合检索的字段
data class Monolight( var powerStatus: PowerStatus = PowerStatus.OFF, var colorTemperature: Int? = null ) { override fun toString(): String = "Monolight [powerStatus=$powerStatus, colorTemperature=$colorTemperature]" }
该类包含以下字段,每个字段都需要分配一个 Codec
powerStatus描述灯光是处于“开启”还是“关闭”状态,我们使用 PowerStatusCodec 进行转换,它将特定的枚举值转换为 BSON 布尔值。colorTemperature描述灯光的颜色,包含一个Int值,我们使用 BSON 库中包含的IntegerCodec。
以下代码示例展示了如何为 Monolight 类实现一个 Codec。请注意,构造函数期望一个 CodecRegistry 实例,它从中检索它需要用于编码和解码其字段的 Codec 实例。
class MonolightCodec(registry: CodecRegistry) : Codec<Monolight> { private val powerStatusCodec: Codec<PowerStatus> private val integerCodec: Codec<Int> init { powerStatusCodec = registry[PowerStatus::class.java] integerCodec = IntegerCodec() } override fun encode(writer: BsonWriter, value: Monolight, encoderContext: EncoderContext) { writer.writeStartDocument() writer.writeName("powerStatus") powerStatusCodec.encode(writer, value.powerStatus, encoderContext) writer.writeName("colorTemperature") integerCodec.encode(writer, value.colorTemperature, encoderContext) writer.writeEndDocument() } override fun decode(reader: BsonReader, decoderContext: DecoderContext): Monolight { val monolight = Monolight() reader.readStartDocument() while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { when (reader.readName()) { "powerStatus" -> monolight.powerStatus = powerStatusCodec.decode(reader, decoderContext) "colorTemperature" -> monolight.colorTemperature = integerCodec.decode(reader, decoderContext) "_id" -> reader.readObjectId() } } reader.readEndDocument() return monolight } override fun getEncoderClass(): Class<Monolight> = Monolight::class.java }
为了确保我们为 Monolight 类的字段提供了 Codec 实例,我们实现了一个自定义的 CodecProvider,如下面的代码示例所示。
class MonolightCodecProvider : CodecProvider { override fun <T> get(clazz: Class<T>, registry: CodecRegistry): Codec<T>? { return if (clazz == Monolight::class.java) { MonolightCodec(registry) as Codec<T> } else null // Return null when not a provider for the requested class } }
在定义转换逻辑之后,我们可以执行以下操作:
将
Monolight实例的数据存储到 MongoDB 中。将 MongoDB 中的数据检索到
Monolight实例中。
以下示例类包含代码,该代码通过传递给 withCodecRegistry() 方法将 MonolightCodecProvider 赋值给 MongoCollection 实例。该示例类还使用 Monolight 类及其关联的 codecs 插入和检索数据。
fun main() = runBlocking { val mongoClient = MongoClient.create("<connection string uri>") val codecRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(IntegerCodec(), PowerStatusCodec()), CodecRegistries.fromProviders(MonolightCodecProvider()), MongoClientSettings.getDefaultCodecRegistry() ) val database = mongoClient.getDatabase("codecs_example_products") val collection = database.getCollection<Monolight>("monolights") .withCodecRegistry(codecRegistry) // Construct and insert an instance of Monolight val myMonolight = Monolight(PowerStatus.ON, 5200) collection.insertOne(myMonolight) // Retrieve one or more instances of Monolight val lights = collection.find().toList() println(lights) }
[Monolight [powerStatus=ON, colorTemperature=5200]]
有关本节中提到的方法和类的更多信息,请参阅以下 API 文档。