编解码器
概述
在本指南中,您可以了解 编解码器 以及支持类,这些类处理 Kotlin 对象与 BSON 数据之间的编码和解码。TheCodec 抽象允许您将任何 Kotlin 类型映射到相应的 BSON 类型。您可以使用此功能将域对象直接映射到 BSON,而不必使用 Document 或 BsonDocument 等类。
您可以在以下部分学习如何使用 Codec 抽象指定自定义编码和解码逻辑,并查看示例实现。
提示
Kotlin 序列化
您可以使用 Kotlin 序列化来处理数据编码和解码,使用带有 @Serializable 类的 Kotlin 序列化,而不是实现自定义编解码器。如果您已经熟悉 kotlinx.serialization 库或更喜欢使用 Kotlin 语法,则可能选择 Kotlin 序列化。有关更多信息,请参阅Kotlin 序列化 指南。
编解码器
Codec 接口包含将 Kotlin 对象序列化和反序列化为 BSON 数据的抽象方法。您可以在实现此接口时定义自定义转换逻辑。
要实现 Codec 接口,需要覆盖 encode()、decode() 和 getEncoderClass() 抽象方法。
encode() 方法
encode() 方法需要以下参数
参数类型 | 描述 |
|---|---|
writer | 实现 BsonWriter 接口的类的实例,这是一个公开写入 BSON 文档方法的接口类型。例如,BsonBinaryWriter 实现将数据写入二进制数据流。使用此实例使用适当的写入方法写入您的 BSON 值。 |
value | 您的实现编码的数据。类型必须与分配给您的实现的类型变量相匹配。 |
encoderContext | 包含编码到BSON中Kotlin对象数据的元信息,包括是否将当前值存储在MongoDB集合中。 |
使用encode()方法,通过BsonWriter实例将编码后的值发送到MongoDB,并且不返回任何值。
decode() 方法
decode()方法返回一个填充了BSON数据值的Kotlin对象实例。此方法需要以下参数:
参数类型 | 描述 |
|---|---|
reader | 实现 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 包含在注册表中。
有关本节中提到的类和接口的更多信息,请参阅以下 API 文档
CodecRegistry
CodecRegistry 是一个不可变的 Codec 实例集合,用于编码和解码 Kotlin 类。您可以使用以下任何 CodecRegistries 类静态工厂方法,从关联类型中包含的 Codec 实例构建 CodecRegistry。
fromCodecs():从Codec实例创建注册表fromProviders():从CodecProvider实例创建注册表fromRegistries():从CodecRegistry实例创建注册表
以下代码显示了如何使用 fromCodecs() 方法构建 CodecRegistry
val codecRegistry = CodecRegistries .fromCodecs(IntegerCodec(), PowerStatusCodec())
前面的示例将以下 Codec 实现分配给 CodecRegistry
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 实例,则 codecRegistry.get() 方法会引发 CodecConfigurationException 异常。
有关本节中类和接口的更多信息,请参阅以下 API 文档
编解码器提供者
CodecProvider 是一个接口,包含创建 Codec 实例并将其分配给 CodecRegistry 实例的抽象方法。类似于 CodecRegistry 接口,BSON 库使用 CodecProvider.get() 方法获取的 Codec 实例在 Kotlin 和 BSON 数据类型之间进行转换。
然而,在您添加包含需要相应 Codec 对象的字段的类的情况下,确保在为整个类创建 Codec 之前,为类的字段实例化 Codec 对象。您可以使用 CodecProvider.get() 方法中的 CodecRegistry 参数传递 Codec 依赖的任何 Codec 实例。
要查看使用 Codec 类进行读写操作的示例,请参阅本指南中的 自定义编解码器示例 部分。
默认编解码器注册表
默认编解码器注册表是一组 CodecProvider 类,用于指定常用 Kotlin 对象和 MongoDB 类型之间的转换。除非您指定了不同的注册表,否则驱动程序将自动使用默认编解码器注册表。
要覆盖一个或多个 Codec 类的行为,但保留其他类的默认编解码器注册表的行为,您可以按优先级顺序指定注册表。例如,假设您想覆盖默认提供程序对枚举类型的 Codec 行为,使用您自定义的 MyEnumCodec。您必须将其添加到注册表列表中,位置在默认编解码器注册表之前,如下例所示
val newRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(MyEnumCodec()), MongoClientSettings.getCodecRegistry() )
有关本节中类和接口的更多信息,请参阅以下 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("Kotlin class name: " + clazz.name)
Kotlin class name: java.util.List
您可以通过在 BsonTypeClassMap 构造函数中指定替换来修改这些映射。以下代码片段显示了如何用 Set 类替换您的 BsonTypeClassMap 实例中 BSON 数组类型的映射
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)
Kotlin class name: java.util.Set
要查看默认映射的完整列表,请参阅 BsonTypeClassMap API 文档。
自定义编解码器示例
本节演示了如何实现 Codec 和 CodecProvider 接口来定义自定义 Kotlin 类的编码和解码逻辑。它展示了如何指定和使用自定义实现以执行读写操作。
以下代码定义了示例数据类 Monolight
data class Monolight( var powerStatus: PowerStatus = PowerStatus.OFF, var colorTemperature: Int? = null ) { override fun toString(): String = "Monolight { powerStatus: $powerStatus, colorTemperature: $colorTemperature }" }
该类包含以下字段,每个字段都需要相应的 Codec 来处理编码和解码
powerStatus:描述设备灯光是"ON"还是"OFF"。对于此字段,使用 PowerStatusCodec,它将PowerStatus枚举值转换为 BSON 布尔值。colorTemperature:以 kelvin 为单位的设备灯光颜色,作为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 类来插入和检索数据
val mongoClient = MongoClient.create(uri) val codecRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(IntegerCodec(), PowerStatusCodec()), CodecRegistries.fromProviders(MonolightCodecProvider()), MongoClientSettings.getDefaultCodecRegistry() ) val database = mongoClient.getDatabase("codec_test") val collection = database.getCollection<Monolight>("monolights") .withCodecRegistry(codecRegistry) // Insert instances of Monolight val monolights = listOf( Monolight(PowerStatus.ON, 5200), Monolight(PowerStatus.OFF, 3000) ) collection.insertMany(monolights) // Retrieve instances of Monolight val results = collection.find() results.forEach { l -> println(l) }
Monolight { powerStatus: ON, colorTemperature: 5200 } Monolight { powerStatus: OFF, colorTemperature: 3000 }
有关本节中提到的方法和类的更多信息,请参阅以下 API 文档