Skip to content

Instantly share code, notes, and snippets.

@championswimmer
Last active July 29, 2023 12:50
Show Gist options
  • Save championswimmer/d7b9be0c26ac88de2455a80117137ec6 to your computer and use it in GitHub Desktop.
Save championswimmer/d7b9be0c26ac88de2455a80117137ec6 to your computer and use it in GitHub Desktop.
Runtime @annotation Processing using Reflection in Kotlin

Runtime @Annotation Processing using Reflection in Kotlin

Let us take a look at Annotations in Kotlin, and start with a simple example of processing annotations at runtime using JVM's reflection framework.

NOTE: This is shown in Kotlin, but is specific to how Kotlin/JVM works, and is not applicable to Kotlin/JS or Kotlin/Native.

Creating an Annotation

First of all, we create annotations in Kotlin using the keywords annotation class.

Here is a newly created Annotation called NiceVar

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
annotation class NiceVar

@Rentention and @Target are called meta annotations. That is to say - they are annotations that are used to annotate other annotations. Target defines what kind of elements (class, field, method etc) can be annotated.

Note that we are using a Retention policy of RUNTIME. If we use SOURCE, then that annotation stays in our source, is useful for our compiler, but gets removed from the runtime.

Annotating some fields

Let us now create a class, and annotate some of its fields with this new annotation we just created.

data class SampleClass (
    var a: Int,
    var b: String,
    @NiceVar var c: Int,
    @NiceVar var d: String
)

Using the annotations at runtime

Finally I am going to create a function called printNiceVars() that will print all the fields of an object that had been annotated with @NiceVar

This is how that is going to look like.

fun main() {

    val obj1 = SampleClass(10, "foo", 20, "bar")

    printAllVars(obj1)
    printNiceVars(obj1)

}

fun printAllVars(obj: Any) {
    println(obj.toString())
}

fun printNiceVars(obj: Any) {
    obj.javaClass.declaredFields.forEach { f ->
        if (f.isAnnotationPresent(NiceVar::class.java)) {
            if (f.isAccessible || f.trySetAccessible())
                println("${f.name} ${f.get(obj)}")
        }
    }
}

As you can see, we iterate through all the fields of the object, and then find out if @NiceVar annotation is present on it or not. If that is the case, we also need to make the field accessible (i.e. if it was private, we cannot directly access it here). And then we print it.

Future Reading

While this gives you an idea how to create and use annotations, this uses reflection, and this is not the best solution performance and secutity wise. In fact most popular libraries like Retrofit, or in Android, DataBinding or Glide use a more powerful strategy called code generation which does the job of annotation processing at compile time itself and the annotations need to stay till run time.

We can take a look at how that works in a future article.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment