什么是方法id
我们日常开发中,可能会遇到这么一个需求,就是想要把每一个方法,都转换为一个个Int类型/Long类型(推荐)来表示,这样的好处是效率非常高,比如在慢方法插桩里面,我们如果直接记录一个方法耗时,同时也要记录是哪个方法产生的耗时对吧,我们一般会以String类型方法名称去记录这么一条信息。但是以String可能会比较麻烦
原因在于String长度是不固定的,如果在插桩场景之下,如果需要插入的方法很多,那么它的消耗也是非常巨大的。
所以程序员们想啊想,既然字符串那么不可靠,那么如果我们能用一个数字代表一个方法,那么无论怎么变化,只要这个方法本身没有改变,那么是不是永远也能找到固定的值?这也通常被称为方法id。
方法id生成
生成方法ID,最主要的是寻找一个映射关系,用于把方法映射成我们定义的ID,这里的ID可以是一个递增的数字,比如我们可以采用一个原子类型,比如AtomicInteger,每次遇到一个新方法的时候,这个数就递增即可,原子类型保证了我们能够在多线程环境下处理方法ID的正确生成,比如我们只需调用incrementAndGet即可处理并发问题。
那么还有一个问题,就是方法,我们怎么把一个方法跟一个id绑定起来呢?处理这个问题前,还有一个问题需要解决,怎么判断方法是不是同一个方法?
这里可以总结成一条公式:方法唯一值 = 方法名+方法签名+类特征
下面我们一一说明这几点:
方法名
一个方法的方法名,比如java/kotlin,就是一个方法的重要特征,当然,我们也不能仅仅依靠方法名去判断,因为java虚拟机是支持重载的,比如fun1(a:Int),跟fun1(a:Int,b:Int),虽然方法名一致,但是也不是同一个方法。因此需要另一个保证,方法签名
方法签名
代表着一个方法参数和返回值的特定字符串,比如一个fun1(param:String),它的签名就是(Ljava/lang/String;)V,括号中间代表着参数,右侧是返回值类型
以上两个,换算成字节码表示,以(ASM 中 MethodNode 为例子,方法名就是name属性,方法签名就是desc)
ini
public class MethodNode extends MethodVisitor {
public int access;
方法名
public String name;
方法签名
public String desc;
public String signature;
public List<String> exceptions;
public List<ParameterNode> parameters;
public List<AnnotationNode> visibleAnnotations;
public List<AnnotationNode> invisibleAnnotations;
public List<TypeAnnotationNode> visibleTypeAnnotations;
public List<TypeAnnotationNode> invisibleTypeAnnotations;
public List<Attribute> attrs;
public Object annotationDefault;
public List<AnnotationNode>[] visibleParameterAnnotations;
public List<AnnotationNode>[] invisibleParameterAnnotations;
public InsnList instructions;
public List<TryCatchBlockNode> tryCatchBlocks;
public int maxStack;
public int maxLocals;
public List<LocalVariableNode> localVariables;
public List<LocalVariableAnnotationNode> visibleLocalVariableAnnotations;
public List<LocalVariableAnnotationNode> invisibleLocalVariableAnnotations;
private boolean visited;
通过方法名+方法签名,我们可以判断一个在类内的方法唯一值,注意这里特别表明是类内,因为如果同样的方法定义,在不同的类中,方法名与函数签名也是一致的,因此我们需要加入第三个维度,类特征
类特征
这里可以指代表一个类的唯一特征,比如我们常见的类名就是,由类加载验证过程,保证了类的唯一性,也可以是其他特有的信息。通过类特征,我们就能区别开不同类的方法
生成代码
通过上面的公式,我们就知道了一个方法唯一值,我们可以取类名+方法名+函数签名的方式拼凑而成,这个时候我们还需要一个容器,存储着两者的映射关系,这里我们可以用一个ConcurrentHashMap<String,Int>存储即可,这也我们可以在多线程环境下,也能收集到正确的方法id。
kotlin
methodMap 是 ConcurrentHashMap<String,Int>,key是方法唯一值,value就是方法id类型
methodId 是一个AtomicInteger类型,用于原子的递增
fun methodCreate(){
classNode.methods.forEach {
if (!methodMap.containsKey(方法唯一值)) {
设置方法id
val id = methodId.incrementAndGet()
methodMap[方法唯一值] = id
}
}
}
生成方法优劣
通过上文的生成方法,我们简单归纳一下好处与坏处
好处 | 坏处 |
---|---|
可以直接在编译时简单生成,上面我们讲到的信息,都是可以在编译时获取到,比如 classNode对应着一个Transform/Transform Action过程的一个类,可以用ASM获取,生成也比较简单,可以支持多线程 | 只能处理编译时就确定的方法,因为依赖编译时生成id,对于动态生成的方法就没有办法 |
方法id生成实践
这种方法id生成思想,由于简单易用,所以也被很多大厂开源所采用,比如
总结
通过本文,我们了解到了方法id以及常见的方法id生成过程,如果大家有多方法插桩的需求,不妨采用方法id的方式代替方法名等信息,希望对你有帮助!