
Ich bin kürzlich auf einen Artikel über ein Problem mit der Java-Serialisierung von Objekten in Kotlin gestoßen. Der Autor schlug vor, das Problem zu lösen, indem er readResolve
jedem Objekt, von dem erbt , eine Methode hinzufügt java.io.Serializable
.
, . , Bundle, is
when
- sealed
.
, , , Kotlin readResolve
, singleton- . , , . Kotlin , ! .
, :
object Example : java.io.Serializable {
fun readResolve(): Any? = Example
}
readResolve
, java.io.Serializable
. , Any?
.
.class
- IDE. , .
. , .
, ; Kotlin .
, JetBrains “kotlin-compiler-embeddable”:
apply plugin: "org.jetbrains.kotlin.jvm"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib"
compileOnly "org.jetbrains.kotlin:kotlin-compiler-embeddable"
}
ComponentRegistrar
, :
class ObjectSerializationComponentRegistrar: ComponentRegistrar {
override fun registerProjectComponents(
project: MockProject,
configuration: CompilerConfiguration
) {
println("Works")
}
}
Kotlin ServiceLoader, ComponentRegistrar
. META-INF/services. AutoService Google, .
# resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
me.shika.ObjectSerializationComponentRegistrar
, :
apply plugin: "org.jetbrains.kotlin.jvm"
dependencies {
kotlinCompilerPluginClasspath project(':kotlin-plugin')
}
Kotlin , . - , “Works” .
, , . Kotlin , JVM ( java.io.Serializable
). ExpressionCodegenExtension
.
-. , , . — , , readResolve
:
class ObjectSerializationJvmGeneration : ExpressionCodegenExtension {
override fun generateClassSyntheticParts(codegen: ImplementationBodyCodegen) {
println("Found ${codegen.descriptor}")
}
}
, .
ProjectExtensionDescriptor<T>
. registerExtension
. - ExpressionCodegenExtension
, .
— ComponentRegistrar
:
override fun registerProjectComponents(
project: MockProject,
configuration: CompilerConfiguration
) {
ExpressionCodegenExtension.registerExtension(
project,
ObjectSerializationJvmGeneration()
)
}
integration-test
, .
-
, , . , , .
fun ClassDescriptor.needsSerializableFix() =
DescriptorUtils.isObject(this)
&& isSerializable()
&& !hasReadMethod()
:
object
-?java.io.Serializable
?readResolve
?
. DescriptorUtils
:
fun ClassDescriptor.isSerializable(): Boolean =
getSuperInterfaces().any {
it.fqNameSafe == SERIALIZABLE_FQ_NAME
|| it.isSerializable()
} || getSuperClassNotAny()?.isSerializable() == true
val SERIALIZABLE_FQ_NAME = FqName("java.io.Serializable")
Serializable
.
— readResolve
:
fun ClassDescriptor.hasReadMethod() =
unsubstitutedMemberScope
.getContributedFunctions(
SERIALIZABLE_READ,
NoLookupLocation.FROM_BACKEND
)
.any {
it.name == SERIALIZABLE_READ
&& it.valueParameters.isEmpty()
}
val SERIALIZABLE_READ = Name.identifier("readResolve")
, . .
, , , . Kotlin ASM - ClassBuilder :
private fun ImplementationBodyCodegen.addReadResolveFunction(
block: InstructionAdapter.() -> Unit
) {
val visitor = v.newMethod(
NO_ORIGIN,
ACC_PUBLIC or ACC_SYNTHETIC,
SERIALIZABLE_READ.identifier,
"()Ljava/lang/Object;",
null,
EMPTY_STRING_ARRAY
)
visitor.visitCode()
val iv = InstructionAdapter(visitor)
iv.apply(block)
FunctionCodegen.endVisit(iv, "JVM serialization bindings")
}
public
synthetic
, IDE. ()Ljava/lang/Object;
. , , -.
- — Example
:
GETSTATIC Example.INSTANCE : LExample;
ARETURN
InstructionAdapter
, , , -, . , :
if (codegen.descriptor.needsSerializableFix()) {
val selfType = codegen.typeMapper.mapType(codegen.descriptor)
codegen.addReadResolveFunction {
getstatic(codegen.className, "INSTANCE", selfType.descriptor)
areturn(selfType)
}
}
Kotlin , -. (, -) , .
: , .
kotlin-compile-testing. Java-. (, test/resources/), .
private val SERIALIZABLE_OBJECT = """
import java.io.Serializable
object Serial : Serializable
""".source()
@Test
fun `adds readResolve to obj extending Serializable`() {
compiler.sources = listOf(SERIALIZABLE_OBJECT)
val result = compiler.compile()
val klass = result.classLoader.loadClass("Serial")
assertTrue(klass.methods.any { it.addedReadResolve()})
}
private fun Method.addedReadResolve() =
name == "readResolve"
&& parameterCount == 0
&& returnType == Object::class.java
&& isSynthetic
readResolve
.
. . , , — :
private object TestObject : Serializable
@Test
fun `object instance is the same after deserialization`() {
assertEquals(TestObject, serializeDeserialize(TestObject))
}
private fun serializeDeserialize(instance: Serializable): Serializable {
val out = ByteArrayOutputStream()
ObjectOutputStream(out).use {
it.writeObject(instance)
}
return ObjectInputStream(
ByteArrayInputStream(out.toByteArray())
).use {
it.readObject() as TestObject
}
}
Kotlin — . , , .
, , : , API - . , Kotlin 1.4.
GitHub. Maven ( ).