Serialisierung von Objekten in Kotlin ein für alle Mal korrigieren


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 readResolvejedem Objekt, von dem erbt , eine Methode hinzufügt java.io.Serializable.


, . , Bundle, is when- sealed .


, , , Kotlin readResolve , singleton- . , , . Kotlin , ! .



, :


object Example : java.io.Serializable {
   // TODO: should be generated
   fun readResolve(): Any? = Example
}

readResolve , java.io.Serializable. , Any?.


.class- IDE. , .



. , .


, ; Kotlin .


, JetBrains “kotlin-compiler-embeddable”:


// kotlin-plugin/build.gradle
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

, :


// integration-test/build.gradle
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}")
       // todo: generate
   }
}

, .


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()

:


  1. object-?
  2. java.io.Serializable?
  3. 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 ( ).


All Articles