文档章节

深入字节码 -- ASM 关键接口 MethodVisitor

哈库纳
 哈库纳
发布于 2014/04/08 16:04
字数 1252
阅读 1.4W
收藏 92

    本文是《深入字节码 -- 使用 ASM 实现 AOP》的后续博文。在上一篇文章中介绍了如何使用 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.DemoClass
Compiled 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."<init>":()V
   4:   return

public 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 <init>
at Method ‘<init>’ Begin...
at Method ‘<init>’End.
at Method main
at Method ‘main’ Begin...
at Method ‘main’End.
---ALL END---

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

    虽然该接口的方法数量如此之多,甚至是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的时候估计已经将多个静态代码块合并到一起了。



© 著作权归作者所有

哈库纳

哈库纳

粉丝 968
博文 84
码字总数 152107
作品 5
杭州
架构师
私信 提问
加载中

评论(11)

哈库纳
哈库纳 博主

引用来自“mrwutong”的评论

编译时使用vars,按说可以将变量名编译进字节码。
请问如何加载jar文件,并将接口内方法的形参名读取到?
例:
interface DemoIF {
public void demoMethod(HttpServletRequest req, HttpServletResponse res) thorws Exception;
}

这个接口被打包进demo.jar
所以我想通过一个main方法读取demo.jar之后,将接口DemoIF的demoMethod方法的形参[req]及[res]读取到。
目前我只能读取实现类的方法形参。

请指教,谢谢。
mv.visitLocalVariable("this", "Lorg/more/test/asm/TestBean;", null, l0, l4, 0);

我记得如果没错的话,拦截这个方法就可以拿到,变量名甚至所有方法中使用到的变量和名称。
m
mrwutong
编译时使用vars,按说可以将变量名编译进字节码。
请问如何加载jar文件,并将接口内方法的形参名读取到?
例:
interface DemoIF {
public void demoMethod(HttpServletRequest req, HttpServletResponse res) thorws Exception;
}

这个接口被打包进demo.jar
所以我想通过一个main方法读取demo.jar之后,将接口DemoIF的demoMethod方法的形参[req]及[res]读取到。
目前我只能读取实现类的方法形参。

请指教,谢谢。
BobAlice
BobAlice
好东西,,希望楼主能继续更新
哈库纳
哈库纳 博主

引用来自“西夏一品堂”的评论

Exception in thread "main" java.lang.UnsupportedClassVersionError: com/agent/my2/User has been compiled by a more recent version of the Java Runtime (class file version 0.0), this version of the Java Runtime only recognizes class file versions up to 52.0
看一下你的 asm 版本,一般情况下 asm 的新版本会兼容老版本。 此外看你的异常信息感觉是类文件里 jvm 版本数据似乎没有写进去。 仔细查一下,一般是写的代码问题。
西夏一品堂
西夏一品堂
Exception in thread "main" java.lang.UnsupportedClassVersionError: com/agent/my2/User has been compiled by a more recent version of the Java Runtime (class file version 0.0), this version of the Java Runtime only recognizes class file versions up to 52.0
哈库纳
哈库纳 博主

引用来自“hanzhankang”的评论

楼主有没有试过用asm分析程序调用链?
做这个不难,拦截下面这几个指令,形成调用堆栈。如果还要把参数记录下来那还要做更多的处理。 int INVOKEVIRTUAL = 182; // visitMethodInsn int INVOKESPECIAL = 183; // - int INVOKESTATIC = 184; // - int INVOKEINTERFACE = 185; // -
华兹格
华兹格
楼主有没有试过用asm分析程序调用链?
悟方向
悟方向

引用来自“郑晨曦”的评论

请问asm支持jdk1.8了吗

最新的asm5支持jdk8
zcx2001
zcx2001
请问asm支持jdk1.8了吗
knightuniverse
knightuniverse
这就是静态织入的原理?
ASM 实现RPC客户端

通常RPC客户端都是通过动态代理来实现的。出于好奇,看看通过ASM生成客户端调用类,性能有没有JDK动态代理更好,想通过实践验证一下。 要想实现RPC的调用,需要做下面几件事情: 1:通过int...

qiujiayu
2016/08/09
196
0
入字节码 -- ASM 关键接口 MethodVisitor

本文是《 深入字节码 -- 使用 ASM 实现 AOP 》的后续博文。在上一篇文章中介绍了如何使用 ASM 动态安插代码到类中,从而简单实现 Aop。文章得到了广大朋友好评,我也希望可以不负众望继续写出...

beibugulf
2016/10/31
1.1K
0
Android自动化埋点技术——替换class字节码中对象创建技术方案分享

2011年4月加入去哪儿网,目前在市场营销部,主要负责商务预装工作,同时在移动端性能监控、自动化打包脚本系统开发维护相关工作。先后参与客户端一期二期大改版,主导了预装开发规范设计和实...

Qunar技术沙龙
2019/05/09
0
0
07-《深度拆解JVM》之常用工具介绍

一、问题引入 在前面的文章中,使用了不少工具来辅助说明,很多人可能还不了解这些工具,不知道都有什么用,应该怎么用。那么今天我便统一做一次具体的介绍。本篇代码较多,你可以多自己实践...

飞鱼说编程
2018/10/09
54
0
深入字节码 -- 计算方法执行时间

深入字节码 -- 计算方法执行时间 什么是字节码 程序通过编译之后生成文件就是字节码集合,正是有这样一种,使得等函数语言只用实现一个编译器即可运行在上。看看一段简单代码。 编译后通过命令...

丢失的羊羔
2016/02/29
94
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring Batch 配置一个步骤(Step)

如我们在 域语言(Domain Language)章节中讨论的内容一致,一个 步骤(Step)是一个独立封装了执行顺序的批量作业(Job),并且包含有用于定义和控制一个批量作业的所有独立信息。 针对这个...

honeymoose
54分钟前
34
0
郑州哪里可以开五金工具发票-郑州新闻网

郑州哪里可以开五金工具发票【1.3.2 - 2.9.3.0 - 0.5.6.8.】李生,adb的全称为Android Debug Bridge,是Android手机通用的一个USB端口。百度CarLife的部分车机采用...

提供格
今天
38
0
郑州哪里可以开五金材料发票-郑州新闻网

郑州哪里可以开五金材料发票【1.3.2 - 2.9.3.0 - 0.5.6.8.】李生,adb的全称为Android Debug Bridge,是Android手机通用的一个USB端口。百度CarLife的部分车机采用...

法放饭
今天
43
0
郑州哪里可以开劳保用品发票-郑州新闻网

郑州哪里可以开劳保用品发票【1.3.2 - 2.9.3.0 - 0.5.6.8.】李生,adb的全称为Android Debug Bridge,是Android手机通用的一个USB端口。百度CarLife的部分车机采用...

多徐重
今天
31
0
centos php ppt转图片

参考:https://blog.csdn.net/aituochang1886/article/details/101167564 安装 Unoconv 参考: https://www.licongying.cn/2018/10/linux-centos-install-unoconv-liboffice/ https://blog.c......

四季变幻
今天
29
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部