You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过反射获取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)来静态解析类文件,找到所有调用目标扩展函数的方法,再递归向上追踪到控制器层。

核心思路

  1. 遍历项目类路径下的所有.class文件;
  2. 用ASM解析每个类的方法,查找调用目标扩展函数的指令(比如invokestatic对应顶层扩展,invokevirtual对应成员扩展);
  3. 记录每个调用方法的全限定名,递归向上查找这些方法的调用者;
  4. 当找到标注了@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 09:05:33