如何通过反射获取Kotlin类的扩展函数及其调用情况?
Kotlin扩展函数反射获取与调用者树形图构建
一、如何反射获取扩展函数
Kotlin扩展函数本质是静态方法,并不会被添加到接收者类(比如MyEntity)的成员中,直接遍历MyEntity的方法肯定找不到。得根据扩展函数的定义位置来查找:
1. 顶层扩展函数(直接写在.kt文件中)
如果扩展函数是顶层函数,比如在MyExtensions.kt里:
// MyExtensions.kt fun MyEntity.myExtensionMethod(): String { return "extension called" }
编译后会自动生成一个名为MyExtensionsKt的类,扩展函数会变成这个类里的静态方法,第一个参数就是MyEntity实例。用反射获取的方式:
import java.lang.reflect.Method fun findTopLevelExtension() { // 加载自动生成的Kt类 val extensionClass = Class.forName("com.example.MyExtensionsKt") // 遍历所有方法,找到目标扩展函数 val extensionMethod = extensionClass.declaredMethods.find { it.name == "myExtensionMethod" && it.parameterTypes[0] == MyEntity::class.java } // 调用扩展函数:第一个参数传MyEntity实例,后续是扩展函数的参数(如果有的话) extensionMethod?.invoke(null, MyEntity()) }
2. 类成员扩展函数(写在某个类内部)
如果扩展函数是某个类的成员:
class ExtensionHolder { fun MyEntity.myExtensionMethod(): String { return "member extension called" } }
这种情况下,扩展函数是ExtensionHolder的成员方法,第一个参数同样是MyEntity实例,获取方式:
fun findMemberExtension() { val holderClass = ExtensionHolder::class.java val extensionMethod = holderClass.declaredMethods.find { it.name == "myExtensionMethod" && it.parameterTypes[0] == MyEntity::class.java } // 调用时需要传ExtensionHolder实例和MyEntity实例 extensionMethod?.invoke(ExtensionHolder(), MyEntity()) }
二、递归查找调用者并构建树形图
运行时反射无法直接获取方法的调用关系,因为调用信息在编译后就变成了字节码指令,需要用字节码分析工具(比如ASM)来静态解析类文件,找到所有调用目标扩展函数的方法,再递归向上追踪到控制器层。
核心思路
- 遍历项目类路径下的所有
.class文件; - 用ASM解析每个类的方法,查找调用目标扩展函数的指令(比如
invokestatic对应顶层扩展,invokevirtual对应成员扩展); - 记录每个调用方法的全限定名,递归向上查找这些方法的调用者;
- 当找到标注了
@Controller的类中的方法时,停止递归,构建树形结构。
代码示例(基于ASM)
首先添加ASM依赖(Maven):
<dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>9.5</version> </dependency>
然后实现调用者追踪和树形构建:
import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes import java.io.File import java.net.URLClassLoader // 存储调用关系:key是被调用方法的全限定名,value是调用它的方法列表 val callGraph = mutableMapOf<String, MutableList<String>>() // 目标扩展函数的全限定名(可通过javap -s 类名.class查看方法描述符) val targetMethodDesc = "com/example/MyExtensionsKt.myExtensionMethod(Lcom/example/MyEntity;)Ljava/lang/String;" // 解析单个类文件 fun analyzeClass(classFile: File) { val classReader = ClassReader(classFile.readBytes()) classReader.accept(object : ClassVisitor(Opcodes.ASM9) { private lateinit var currentClassName: String override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array<out String>?) { currentClassName = name super.visit(version, access, name, signature, superName, interfaces) } override fun visitMethod(access: Int, name: String, descriptor: String, signature: String?, exceptions: Array<out String>?): MethodVisitor { val currentMethodDesc = "$currentClassName.$name$descriptor" return object : MethodVisitor(Opcodes.ASM9) { override fun visitMethodInsn(opcode: Int, owner: String, name: String, descriptor: String, isInterface: Boolean) { val calledMethodDesc = "$owner.$name$descriptor" // 检查是否调用了目标扩展函数 if (calledMethodDesc == targetMethodDesc) { callGraph.getOrPut(targetMethodDesc.replace("/", ".")) { mutableListOf() }.add(currentMethodDesc.replace("/", ".")) } // 递归追踪已记录方法的调用者 callGraph.keys.find { it.replace(".", "/") == calledMethodDesc }?.let { callGraph.getOrPut(it) { mutableListOf() }.add(currentMethodDesc.replace("/", ".")) } super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) } } } }, ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES) } // 遍历类路径下的所有类文件 fun scanClassPath() { val classLoader = Thread.currentThread().contextClassLoader as URLClassLoader classLoader.urLs.forEach { url -> val file = File(url.toURI()) if (file.isDirectory) { file.walkTopDown().filter { it.name.endsWith(".class") }.forEach { analyzeClass(it) } } } } // 树形节点数据类 data class CallNode(val method: String, val children: MutableList<CallNode> = mutableListOf()) // 构建调用树形结构 fun buildCallTree(targetMethod: String): CallNode { val node = CallNode(targetMethod) callGraph[targetMethod]?.forEach { caller -> val className = caller.substringBefore(".") val clazz = Class.forName(className) // 判断是否是控制器层方法 if (clazz.isAnnotationPresent(org.springframework.stereotype.Controller::class.java)) { node.children.add(CallNode(caller)) } else { node.children.add(buildCallTree(caller)) } } return node } // 打印树形结构 fun printCallTree(node: CallNode, indent: String = "") { println("$indent${node.method}") node.children.forEach { printCallTree(it, "$indent ") } } // 执行入口 fun main() { scanClassPath() val callTree = buildCallTree(targetMethodDesc.replace("/", ".")) printCallTree(callTree) }
说明
- 要根据实际情况修改
targetMethodDesc,可以通过javap -s MyExtensionsKt.class命令查看方法的描述符; - 控制器层的判断逻辑可根据项目调整,比如适配
@RestController或自定义注解; - 示例仅实现基础调用追踪,实际项目中需处理重载方法、lambda调用等复杂场景。
内容的提问来源于stack exchange,提问作者Frappuccino
相关产品推荐
相关产品推荐

