基于ASM实现方法参数名的收集
open class LocalVariableTableParameterNameDiscoverer : ParameterNameDiscoverer {
companion object {
private val NO_DEBUG_INF_MAP = emptyMap<Executable, Array<String>>()
}
private val parameterNamesCache: MutableMap<Class<*>, Map<Executable, Array<String>>> = ConcurrentHashMap()
override fun getParameterNames(method: Method): Array<String>? {
return doGetParameterNames(method)
}
override fun getParameterNames(constructor: Constructor<*>): Array<String>? {
return doGetParameterNames(constructor)
}
private fun doGetParameterNames(executable: Executable): Array<String>? {
val declaringClass = executable.declaringClass
val map = parameterNamesCache.computeIfAbsent(declaringClass, this::inspectClass)
return if (map == NO_DEBUG_INF_MAP) null else map[executable]
}
private fun inspectClass(clazz: Class<*>): Map<Executable, Array<String>> {
val classFile = clazz.name.substring(clazz.name.lastIndexOf(".") + 1) + ".class"
val stream = clazz.getResourceAsStream(classFile)
if (stream == null) {
// log...
return NO_DEBUG_INF_MAP
}
try {
val classReader = ClassReader(stream)
val paramNameMap = ConcurrentHashMap<Executable, Array<String>>()
classReader.accept(ParameterNameDiscoveringVisitor(clazz, paramNameMap), 0)
return paramNameMap
} catch (ex: IOException) {
// ignored
} catch (ex: IllegalArgumentException) {
// ignored
} finally {
try {
stream.close()
} catch (ignored: IOException) {
// ignored
}
}
return NO_DEBUG_INF_MAP
}
/**
* 进行参数名的获取的[ClassVisitor]
*
* @param map 收集参数名的map(Key-方法, Value-该方法的参数名列表)
*/ private class ParameterNameDiscoveringVisitor(
private val clazz: Class<*>,
private val map: MutableMap<Executable, Array<String>>
) : ClassVisitor(SpringAsmInfo.ASM_VERSION) {
/**
* 访问类上的一个方法, 对方法当中的数据进行采集
*
* @param access accessFlag
* @param name 方法名称, 包含构造器和静态初始化代码块方法 , eg: "<init>", "<clinit>", "foo" * @param descriptor 方法签名 eg: "(Ljava/lang/String;)V", "()V" * @param exceptions exceptionTables
*/ override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor? {
// constructor => name=<init>
// static init block => name=<clinit> if (name == "<clinit>") {
return null
}
return LocalVariableTableVisitor(clazz, map, name, descriptor, isStatic(access))
}
private fun isStatic(access: Int): Boolean = (Opcodes.ACC_STATIC and access) != 0
}
/**
* 访问方法的局部变量表的方式进行参数名的收集
*
* @param clazz 访问的类
* @param map 采集结果的Map, Key是方法, Value是该方法的参数名列表
* @param name 方法名称
* @param desc 方法描述符
*/
private class LocalVariableTableVisitor(
private val clazz: Class<*>,
private val map: MutableMap<Executable, Array<String>>,
private val name: String,
desc: String?,
private val staticMethod: Boolean,
) : MethodVisitor(SpringAsmInfo.ASM_VERSION) {
/**
* 根据方法描述符desc可以获取到参数类型列表
*/
private val args = Type.getArgumentTypes(desc)
/**
* 计算局部变量表当中每个参数所在的槽位slotIndex, index=参数索引, value=该参数在局部变量表当中的第几个slot
*/ private val lvtSlotIndex: IntArray = computeLocalVariableTableIndex()
/**
* 参数名列表, index=参数索引, value=该参数的名字
*/
private val parameterNames = Array<String?>(args.size) { null }
/**
* 是否存在有局部变量表的信息, 如果访问到了局部变量表, 那么设置为true
*/ private var hasLocalVariableInfo = false
/**
* 访问其中一个局部变量
*
* @param name 局部变量表的name, 如果是实例方法, 第一个参数为"this"
* @param descriptor 参数类型的描述信息, 比如"[I", "Ljava/lang/String;"
* @param index 当前局部变量slot的位置index
*/ override fun visitLocalVariable(
name: String,
descriptor: String,
signature: String?,
start: Label?,
end: Label?,
index: Int
) {
this.hasLocalVariableInfo = true
for (i in lvtSlotIndex.indices) {
if (lvtSlotIndex[i] == index) {
parameterNames[i] = name
}
}
}
@Suppress("UNCHECKED_CAST")
override fun visitEnd() {
if (hasLocalVariableInfo || (staticMethod && parameterNames.isEmpty())) {
val executable = resolveExecutable()
map[executable] = parameterNames as Array<String>
}
}
private fun resolveExecutable(): Executable {
val argumentTypes = args.map { ClassUtils.resolveClassName(it.className, clazz.classLoader) }.toTypedArray()
if (name == "<init>") {
return clazz.getDeclaredConstructor(*argumentTypes)
} else {
return clazz.getDeclaredMethod(name, *argumentTypes)
}
}
private fun computeLocalVariableTableIndex(): IntArray {
var nextParamIndex = if (staticMethod) 0 else 1
return this.args.indices.map {
val currentIndex = nextParamIndex
nextParamIndex = if (it == Type.LONG || it == Type.DOUBLE) nextParamIndex + 2 else nextParamIndex + 1
return@map currentIndex
}.toIntArray()
}
}
}
评论