vendredi 10 juillet 2020

Kotlin: set annotated lateinit var using reflection

Imagine that I try to build simple dependency injection lib. Its Injector class, when called on a specific class, should inject all properties annotated with @Service annotation.

For example, given this client:

class ClientA {
    @Service private lateinit var service1: Service1
    @Service private lateinit var service2: Service2
    private lateinit var service3: Service3
}

a call to injector.inject(ClientA()) should result in service1 and service2 being set (but not service3). Let's assume that Injector knows how to construct these objects.

My question is how to write the code that parses class' properties, checks their annotations and sets them in Kotlin?

Since I'm on Android, I tried to go through Java reflection:

fun inject(client: Any) {
    val clientClass = client::class.java
    val fields = clientClass.declaredFields
    for (field in fields) {
        if (isAnnotatedForInjection(field)) {
            injectField(client, field)
        }
    }
}

private fun isAnnotatedForInjection(field: Field): Boolean {
    val fieldAnnotations = field.annotations
    for (annotation in fieldAnnotations) {
        if (annotation is Service) {
            return true
        }
    }
    return false
}

The problem is that fieldAnnotations is empty. Converting ClientA's code to Java I see the following:

public final class ClientA {
   private Service1 service1;
   private Service2 service2;
   private Service3 service3;

   /** @deprecated */
   // $FF: synthetic method
   @Service
   private static void service1$annotations() {
   }

   /** @deprecated */
   // $FF: synthetic method
   @Service
   private static void service2$annotations() {
   }
}

Looks like Kotlin compiler creates static methods to aggregate properties' annotations. With this info, I can write some ugly code to make it work using Java's reflection API, but there must be a cleaner way, right?





Aucun commentaire:

Enregistrer un commentaire