ASM核心API 类解析用法(4)

发表于2019-08-14,长度2095, 195个单词, 5分钟读完
Flag Counter

前面介绍了ClassReader 的简单用法,这篇结合ClassVisitor 一起使用一下。

本篇中用到的例子都上传到了https://github.com/davelet/asm101

ClassReader 和 ClassVisitor 的结合点是ClassReader 的 accept 方法。这个方法的第一个参数是一个ClassVisitor 的对象,ClassVisitor试一个抽象类,所以需要我们实例化:

public class ClassOut extends ClassVisitor {
    public ClassOut() {
        super(Opcodes.ASM7);
    }
}

与ClassReader 结合的方法很简单:

ClassReader cr = new ClassReader("com.asmtest.asm.Klazz");
cr.accept(new ClassOut(), 0);

在《ASM核心API简介》中的“调用顺序”说过ClassVisitor 中的方法只能以以下顺序调用:先调用且调用一次visit方法,然后是0或1次visitSource,然后是0或1次visitOuterClass,然后是0或多次visitAnnotation或visitAttribute,然后是0或多次visitInnerClass或visitField或visitMethod,最后必须调用一次visitEnd。 当然跟ClassReader结合使用的话,ClassReader会帮我们调用,但是这些方法的使用还要我们自己实现。否则你运行上面的代码什么也得不到。

最简单的,我们先自定义一下visit方法。这个方法有多个参数:

public void visit(
      final int version, \\ 类编译的版本,从Java1到Java13依次是45到57。我使用的Java11,所以会打印55
      final int access, \\ 访问控制,上篇提到了
      final String name, \\ 类名
      final String signature, \\ 签名,泛型类相关的名字
      final String superName, \\ 父类
      final String[] interfaces) \\ 接口数组

在ClassOut中重写visit方法,比如:

@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
    System.out.println(name + " extends " + superName + " {");
}

输出:

com/asmtest/asm/Klazz extends com/asmtest/asm/Me {

之前说过,类中只有全限定名,没有包和引入。

你可以自己试一下签名打印出来的效果。让Klazz继承一个有泛型参数的父类即可。


类似的,我们可以重新其他方法。

visitSource是查看源文件的名称的。咦,类名称不就是源文件的名字吗?不一定哦,想一下为什么?(想到了下面留言)

visitNestHost是访问内部类的,只有内部类才会调用这个方法。与之相关的还有一个方法是visitInnerClass,这个方法可以同时查看内部类和外部类。

反过来的访问方法是visitNestMember,当类有内部类时会调用这个方法,有几个内部类就调用几次。而且也会调用visitInnerClass方法。

这几个都是没有返回值的方法。其他的,比如访问字段的、注解的、方法的、模块的,都要返回值,需要再访问一次返回值(当然不一定是一次)。

比如重写visitMethod方法:

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
    System.out.println("方法是" + name + descriptor);
    return super.visitMethod(access, name, descriptor, signature, exceptions);
}

输出类似于:

方法是<init>()V
方法是v()V
方法是i()I
方法是lambda$v$1()V
方法是lambda$static$0()V
方法是<clinit>()V

这些标记在《Java类文件简析》中都介绍过。不过你可能更好奇的是,最先调用的是init方法,最后调用的是clinit方法。关于它们的区别可以去谷歌上百度一下。

其他的方法都可以自己试一下。

Written on August 14, 2019
分类: dev, 标签: java asm
如果你喜欢,请赞赏! davelet