博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
入字节码 -- ASM 关键接口 MethodVisitor
阅读量:6712 次
发布时间:2019-06-25

本文共 4024 字,大约阅读时间需要 13 分钟。

hot3.png

本文是《  》的后续博文。在上一篇文章中介绍了如何使用 ASM 动态安插代码到类中,从而简单实现 Aop。文章得到了广大朋友好评,我也希望可以不负众望继续写出可以得到大家认可的更多相关文章。本文主要讲解  ASM 核心接口方法和其参数意义 。另外本文也可用做参考手册使用。

在上一篇文章中着重介绍了  ClassVisitor 接口,在本文将重点介绍一下 MethodVisitor 接口。前文提到过Class的文件结构类似

Class    Annotation        Annotation            ...    Field        Annotation            ...    Method        Annotation            ...

当ASM的 ClassReader读取到Method时就转入 MethodVisitor接口处理。方法的定义,以及方法中指令的定义都会通过MethodVisitor接口通知给程序。我们假设有下面这样的一个类:

public class DemoClass {	public static void main(String[] args) {		System.out.println();	}}

通过Javap可以得到下面这样的输出:

$ javap -c classtest.DemoClassCompiled from "DemoClass.java"public class classtest.DemoClass extends java.lang.Object{public classtest.DemoClass();  Code:   0:   aload_0   1:   invokespecial   #8; //Method java/lang/Object."
":()V 4: returnpublic static void main(java.lang.String[]); Code: 0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream; 3: invokevirtual #22; //Method java/io/PrintStream.println:()V 6: return}

可以看出其实Java编译完这个类之后是产生了两个方法。其中一个是第四行表示的“public classtest.DemoClass();”它是构造方法。和第十行表示的“main”方法。下面这段例子用来扫描这个类的这两个方法,我们的扫描逻辑很简单就是当遇到一个定义的方法时输出这个方法名。

public class DemoClassTest {    public static void main(String[] args) throws IOException {        ClassReader cr = new ClassReader(DemoClass.class.getName());        cr.accept(new DemoClassVisitor(), ClassReader.SKIP_DEBUG);        System.out.println("---ALL END---");    }}class DemoClassVisitor extends ClassVisitor {    public DemoClassVisitor() {        super(Opcodes.ASM4);    }    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {        System.out.println("at Method " + name);        //        MethodVisitor superMV = super.visitMethod(access, name, desc, signature, exceptions);        return new DemoMethodVisitor(superMV, name);    }}class DemoMethodVisitor extends MethodVisitor {    private String methodName;    public DemoMethodVisitor(MethodVisitor mv, String methodName) {        super(Opcodes.ASM4, mv);        this.methodName = methodName;    }    public void visitCode() {        System.out.println("at Method ‘" + methodName + "’ Begin...");        super.visitCode();    }    public void visitEnd() {        System.out.println("at Method ‘" + methodName + "’End.");        super.visitEnd();    }}
  1. 上面这段程序我们首先在第三行使用 ClassReader 去读取  DemoClass 类的字节码信息。
  2. 其次通过“ cr.accept(new DemoClassVisitor(), ClassReader.SKIP_DEBUG); ”方法开始Visitor扫描整个字节码。
  3. SKIP_DEBUG选项 的意义是在扫描过程中掠过所有有关行号方面的内容。
  4. 在DemoClassVisitor类中我们重写了visitMethod方法,当遇到方法的时候打印出方法名。
  5. 随后我们返回DemoMethodVisitor对象,用以输出方法的开始和结束。

上面这段程序的输出如下:

at Method 
at Method ‘
’ Begin...at Method ‘
’End.at Method mainat Method ‘main’ Begin...at Method ‘main’End.---ALL END---

下面是这个MethodVisitor接口的所有方法定义。本文只会介绍主要的方法,因此不会逐个对方法做依次介绍:

iuQjIj.png!web

虽然该接口的方法数量如此之多,甚至是ClassVisitor接口的3倍以上。但是值得我们关心的接口只有下面这几个,其余的都是和代码有关系:

MethodVisitor.visitCode();MethodVisitor.visitMaxs(maxStack, maxLocals);MethodVisitor.visitEnd();
  • 第一个方法:表示ASM开始 扫描这个方法。
  • 第二个方法:该方法是 visitEnd之前调用的方法,可以反复调用。用以确定类方法在执行时候的堆栈大小。
  • 第三个方法:表示方法输出 完毕。

构造方法:

关于方法名或许读者注意到了在扫描这个类的时候,有一个特殊的方法被扫描到了“<init> ”,这个方法是传说中的构造方法。 当Java在编译的时候没有发现类文件中有构造方法的定义会为其创建一个默认的无参构造方法。这个“<init>”就是 那个由系统添加的构造方法。现在我们为类填写一个构造方法如下:

public class DemoClass {    public DemoClass(int a) {            }    public static void main(String[] args) {        System.out.println();    }}

再次扫描这个类,你会发现它的结果和刚才是一样的,这是由于我们编写的构造方法替换了系统默认生成的那个。

静态代码块:

在Class我们接触过用“static {  }”包含的代码,这个是我们常说的静态代码块。这个代码快ASM在扫描字节码的时候也会遇到它,大家可千万别以为这真的是一个什么代码块。所有的静态代码快最后都会放到“<clinit>”方法中。

静态代码快只有一个,现有下面这个的一个类。在编写这个类的时候我有意的写了两个不同的静态代码块的类:

public class DemoClass {    static {        int a;        System.out.println(11);    }    public static void main(String[] args) {        System.out.println();    }    static {        int a;        System.out.println(22);    }}

ASM在扫描这个类的时候你会发现虽然类中存在多个静态代码快,但是最后类文件中只会出现了一个“ <clinit>”方法。JVM在编译Class的时候估计已经将多个静态代码块合并到一起了。

转载于:https://my.oschina.net/chendongj/blog/778579

你可能感兴趣的文章
std::string的工具函数 - 用ostringstream实现repeat
查看>>
Android官方开发文档Training系列课程中文版:分享简单数据之从其它APP接收简单数据...
查看>>
X Window没有在linux内核实现的原因
查看>>
IN2Windows: Case of the Unexplained Access Denied
查看>>
存储分类
查看>>
linux的历史及大事年表
查看>>
·php字符串
查看>>
高级运维工程师的打怪升级之路
查看>>
Ubuntu16.04下Scrapy环境的搭建
查看>>
Flex使用<mx:Tree>控件创建树(可添加和删除节点)
查看>>
Netty4 之 简单搭建HTTP服务
查看>>
在iframe窗体内 获取父级的元素;;在父窗口中获取iframe中的元素
查看>>
尘埃落定 所有你想了解的Apple Watch综述
查看>>
Ubuntu任务栏如何设置为底部
查看>>
Log4j分级别记录日志文件
查看>>
Firewalld防火墙
查看>>
Kafka 入门 and kafka+logstash 实战应用
查看>>
LoRaWAN 基础知识与关键技术
查看>>
文件服务器之Branchcache单域多站点环境搭建
查看>>
Windows下Libvirt Java API使用教程(一)- 开发环境部署
查看>>