编解码器
Overview
在本指南中,您可以了解编解码器以及支持类,它们在 MongoDB Kotlin 驱动程序中处理 Kotlin 对象与 BSON 数据的编码和解码。 Codec
抽象类允许您将任何 Kotlin 类型映射到相应的 BSON 类型。 您可以使用它将域对象直接映射到 BSON 或从 BSON 直接映射到域对象,而无需使用数据类或基于中间映射的对象,例如Document
或BsonDocument
。
可以了解如何使用 Codec
抽象类来指定自定义编码和解码逻辑,并在以下部分中查看示例实施:
编解码器
Codec
接口包含用于将 Kotlin 对象序列化和反序列化为 BSON 数据的抽象方法。 您可以在该接口的实现中定义 BSON 和 Kotlin 对象之间的转换逻辑。
要实现 Codec
接口,请覆盖 encode()
、 decode()
和 getEncoderClass()
抽象方法。
encode()
方法需要使用以下参数:
Parameter Type | 说明 |
---|---|
writer | 实施 BsonWriter 的类的实例,该接口类型公开用于编写 BSON 文档的方法。例如,BsonBinaryWriter 实施写入二进制数据流。使用此实例通过适当的写入方法写入 BSON 值。 |
value | 实施所编码的数据。该类型必须与分配给您的实施的类型变量相匹配。 |
encoderContext | 包含有关编码为 BSON 的 Kotlin 对象数据的元信息,包括是否将当前值存储在 MongoDB 集合中。 |
此方法使用 BsonWriter
实例将编码值发送到 MongoDB,并且不返回值。
decode()
方法返回用 BSON 数据中的值填充的 Kotlin 对象实例。 此方法需要以下参数:
Parameter Type | 说明 |
---|---|
bsonReader | 实现 BsonReader 的类的实例,该接口类型公开用于读取 BSON 文档的方法。例如,BsonBinaryReader 实施从二进制数据流中读取。 |
decoderContext | 包含有关解码为 Kotlin 对象的 BSON 数据的信息。 |
getEncoderClass()
方法返回 Kotlin 类的类实例,因为 Kotlin 由于类型擦除而无法推断类型。
请参阅以下代码示例,这些示例展示了如何实施自定义 Codec
。
PowerStatus
枚举包含值“打开”和“关闭”来表示电气开关的状态。
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
中,其中包含Codec
与其适用的 Kotlin 对象类型之间的映射。 继续访问本页的CodecRegistry部分,查看如何包含Codec
。
有关此部分中的类和接口的更多信息,请参阅以下 API 文档:
CodecRegistry
CodecRegistry
是 Codec
实例的不可变集合,这些实例对其指定的 Kotlin 类进行编码和解码。您可以使用以下任一 CodecRegistries
类静态工厂方法,从关联类型中包含的 Codec
实例构造 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
)所需的任何 Codec
实例传递给MonolightCodec
:
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
类进行读写操作的可运行示例,请参阅本指南的自定义编解码器示例一节。
默认编解码器注册表
默认编解码器注册表是一组CodecProvider
类,用于指定常用 Kotlin 和 MongoDB 类型之间的转换。 除非您指定其他注册表,否则驱动程序会自动使用默认编解码器注册表。
如果你需要覆盖一个或多个 Codec
类的行为,但保留其他类的默认编解码器注册表中的行为,则可以按优先顺序指定所有注册表。例如,假设你想要使用自定义 MyEnumCodec
替换枚举类型的 Codec
的默认提供商行为,则必须将其添加到默认编解码器注册表之前的注册表列表中,如以下示例所示:
val newRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(MyEnumCodec()), MongoClientSettings.getDefaultCodecRegistry() )
有关此部分中的类和接口的更多信息,请参阅以下 API 文档部分:
BsonTypeClassMap
BsonTypeClassMap
类包含 BSON 和 Kotlin 类型之间的推荐映射。 您可以在自定义 Codec
或 CodecProvider
中使用此类来帮助您管理哪些Kotlin类型可将BSON types解码为实现 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
请参阅 bsonTy peClassMap 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 }
为了确保字段的 Codec
实例可用于 Monolight
,我们实施了以下代码示例中所示的自定义 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
以下示例类中包含的代码通过将 MonolightCodecProvider
传递给 withCodecRegistry()
方法,从而将其赋值给 MongoCollection
实例。该示例类还使用 Monolight
类和关联的编解码器插入和检索数据:
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 文档: