通过本次实验对 ASM 这个字节码框架有一个基本的了解。实验必须是简单明了,方便重现的。引用一段话很好的概括了 ASM 的功能
可以负责任的告诉大家,ASM只不过是通过 “Visitor” 模式将 “.class” 类文件的内容从头到尾扫描一遍。因此如果你抱着任何更苛刻的要求最后都将失望而归。
实验平台信息:
MacOS + IDEA + ASM Bytecode Outline 插件
输出 Class 方法
准备测试用 class,通过 ASM 输出 class 中的方法名称
1 |
|
右键准备的测试文件,选中 ‘Show bytecode outline’ 选项,点击 Bytecode tab, 查看内容可以看到字节码如下
1 | // class version 52.0 (52) |
测试用例
1 | public class ASMTest { |
终端输出
1 | --- START --- |
想要理解 ASM 运行方式,需要结合前面的 bytecode 内容。比如 visitLocalVariable
方法其实就是将 bytecode 里面对应的 LOCALVARIABLE 信息打印出来。
MethodVisitor 的 visitMethodInsn 方法简单例子
基本使用
根据查到的资料,该方法可以知道当前的方法调用了其他类的什么方法,设计用例如下: Class A 有 method a, Class B 有 method b, a 中包含对 b 的调用,使用 visitMethodInsn 解析 a 方法是应该可以拿到这层关系
1 | public class ClassA { |
class A 的 bytecode 显示如下
1 | // class version 52.0 (52) |
可以看到在 methodA()V
block 里有对 ClassB 的方法调用说明 INVOKEVIRTUAL com/jzheng/asmtest/ClassB.methodB ()V
,通过它我们可以知道当前方法对其他类方法的调用
测试用例:
1 | public class ASMTest { |
测试 itf 参数
visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)
- desc: 方法参数和返回值类型,
()
内为参数,外面是返回值 - itf 方法是否来自接口,如下面所示的例子,当子类实现接口,通过子类调用方法时,值为 false,当强转为接口时值为 true。 值的注意的是,继承的方法也是 false。
1 | import org.objectweb.asm.ClassReader; |
visitInvokeDynamicInsn 用以检测 lambda 表达式
1 | /** |
注意参数列表中的 bsmArgs, 其中的 Handle 可能是你想要的, 列表中的 bsm 是一个固定值,看着像是 lambda 的指代
修改方法
实验内容:准备一个 HelloWorld.class 可以打印出 ‘Hello World’ 字样。通过 ASM 框架使他在打印之前和之后都输出一些 debug 信息,调用时可以使用反射简化实验。
测试用 class
1 | public class HelloWorld { |
测试用例,通过反射拿到测试方法并调用查看输出。
1 | import java.lang.reflect.Method; |
预期目标:通过 ASM 修改目标 class 使得输出为 ‘Test start \n Hello World… \n Test end’,对应的 java code:
1 | public class Expected { |
选中 java 文件,右键 -> Show Bytecode Outline 选中 ASMifield tab 可以看到转化后的代码
1 | package asm.sorra.tracesonar.main.aopsample; |
其中类似如下的代码使一些行号和变量的处理,可以删掉不要,不影响结果
1 | Label l0 = new Label(); |
将自动生成的文件里的冗余语句删掉,加一个 main 方法,生成文件并存放到根目录下
1 | public class ExpectedDump { |
运行该 Java 文件,可以看到 project 的根目录下有生成一个名为 ‘Expected.class’ 的文件,在 IDEA 里面浏览它,编辑器会自动给出反编译结果,可以发现,在目标语句前后已经加上了我们要的 ‘Test Start/End’ 的 debug 语句了。