编解码器
概述
在本指南中,您可以了解有关 编解码器 以及在 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 文档。