Issue
This Content is from Stack Overflow. Question asked by delimiter
I’m trying to notify a central class on startup about the existence of some classes that will be used during runtime. My idea is to use reflection: scan for annotation (or interface implementation) and call a method from the companions object of those classes.
As suggested in [1] I am using classgraph
but I’m totally open to alternatives.
package com.test
import io.github.classgraph.ClassGraph
import io.github.classgraph.ClassInfo
import io.github.classgraph.ScanResult
import kotlin.reflect.KFunction
import kotlin.reflect.jvm.kotlinFunction
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class OnStartup
// a class that will be instantiated a lot of times during runtime
data class SomeClass(val name: String) {
companion object {
@OnStartup
fun onStartup() {
// notify someone at startup about our existence
}
}
}
fun main() {
val scanResult: ScanResult = ClassGraph().enableAllInfo().acceptPackages("com.test").scan()
scanResult
.getClassesWithMethodAnnotation(OnStartup::class.java.name)
.filter { it.isStatic }
.flatMap { findStartupMethods(it) }
.forEach { it.call() }
}
private fun findStartupMethods(classInfo: ClassInfo): List<KFunction<*>> {
return classInfo.methodInfo.filter { function ->
function.hasAnnotation(OnStartup::class.java)
}.mapNotNull { method ->
method.loadClassAndGetMethod().kotlinFunction
}
}
The problem is, that the code exits with
Exception in thread "main" java.lang.IllegalArgumentException: Callable expects 1 arguments, but 0 were provided.
From reading the Kotlin Docs and [2] my guess is that I should handover the companionObjectInstance
as parameter. But I have absolutely no idea how to get it…
Any help is really appreciated.
[1] Getting a list of annotated functions in Kotlin using reflection
[2] Kotlin invoke companion function with reflection
Solution
This should work:
inline fun <reified T> test() {
val companionObject = T::class.companionObject
if (companionObject != null) {
val body: Person.() -> Unit = { println("body called on $name!") }
val companionInstance = T::class.companionObjectInstance
val functionEx = companionObject.functions.first { it.name.equals("sleep") }
functionEx.call(companionInstance, body)
}
}
To call the function using call(...)
, you need to pass the companion object instance as the first argument (which stands for the sleep
function’s receiver) and an instance of the function type Person.() -> Unit
as the second one.
Note that companionObject
returns not the companion object instance but its KClass
. To get the instance, use companionObjectInstance
instead.
This Question was asked in StackOverflow by BhaskerYadav and Answered by hotkey It is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.