本文记录对ASM 字节码操控框架的梳理和总结,方便需要时查看。
一、什么是ASM
ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
ASM设计了两种类型,一种是基于Tree API,一种是基于Visitor API(visitor pattern)
- Tree API将class的所有结构信息读取到内存中,构建一个树形结构,然后需要处理Method、Field等元素时,到树形结构中定位到某个元素,进行操作,然后把操作再写入新的class文件。
- Visitor API则将通过接口的方式,分离读class和写class的逻辑,一般通过一个ClassReader负责读取class字节码,然后ClassReader通过一个ClassVisitor接口,将字节码的每个细节按顺序通过接口的方式,传递给ClassVisitor(你会发现ClassVisitor中有多个visitXXXX接口),这个过程就像ClassReader带着ClassVisitor游览了class字节码的每一个指令。
Visitor Api 相比Tree Api 访问效率要高很多,本节重点阐述Tree Api的基本用法。
二、ASM Visitor Api
ASM 是基于访问者模式的,基本组成如下:
- Opcodes接口定义了一些常量,尤其是版本号,访问标示符,字节码操作码等信息;
- ClassReader 用于读取Class文件。
主要用于Class文件的分析,可接受一个ClassVisitor;ClassReader会将解析过程中产生的类的部分信息,比如访问标识符,字段,方法逐个送入ClassVisitor,后者在接收到对应的信息后,进行各自的处理。
- ClassVisitor 是一个抽象类,用于访问类的属性信息、方法信息以及其他信息。通常会定义一个ClassVisitor的子类(Adapter类),用于将改变现有类的一些信息(属性、方法等),将改变后的信息沿着访问链条继续传递下去,获得的就是改变后的class。
- ClassWriter 是ClassVisitor的子类,位于Visitor链条的末端,用于将类信息转换成ByteArray,或者写入到一个输出流中。
ClassReader、ClassAdapter、ClassWriter的访问链条如下所示:
常规调用链:ClassReader->ClassAdapter->ClassWriter->toByteArray()
//移除原有类中的一个方法
fun scanClassRemoveMethod(file: File, out: File) {
var inputStream = FileInputStream(file)
var outputStream = FileOutputStream(out)
//构造ClassReader对象
var cr = ClassReader(inputStream)
//构造ClassWriter对象
var cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
//构造ClassAdapter对象,指定其传递链的下一级为ClassWriter
var classAdapter =
RemoveMethodClassVisitor(Opcodes.ASM7, cw)
//ClassReader读取类的信息,指定读取到的类信息传递到ClassAdapter
cr.accept(classAdapter, ClassReader.EXPAND_FRAMES)
//最终的类转换成ByteArray
var newClassBytes = cw.toByteArray()
outputStream.write(newClassBytes)
inputStream.close()
outputStream.close()
}
2.1、事件访问顺序
2.1.1、ClassVisitor访问顺序
ClassVisitor的方法调用遵循一定的顺序,如下
visit
visitSource?
visitOuterClass?
( visitAnnotation | visitAttribute )*
( visitInnerClass | visitField | visitMethod )*
visitEnd
This means that visit must be called first, followed by at most one call to visitSource, followed by at most one call to visitOuterClass, followed by any number of calls in any order to visitAnnotation and visitAttribute, followed by any number of calls in any order to visitInnerClass, visitField and visitMethod, and terminated by a single call to visitEnd.
2.1.2、MethodVisitor访问顺序
visitMethod()
visitCode()
....
visitInsn(RETURN)
visitMax()
visitEnd()
visitCode() 为访问Method的开始 visitMax() 为访问Method的结束 visitEnd() 为MethodVisitor最后调用的方法
2.2、如何生成一个全新的Class
以自定义Comparable类为例,我们看怎么通过ASM 来从0开始,生成一个Comparable类:
package com.sogou.iot.testasm;
/**
* 文件名:Comparable
* 创建者:baixuefei
*/
public class Comparable {
int LESS = -1;
int compareTo(Object o){
return -1;
}
}
ClassWriter类 按照一定的顺序 调用接口,就可以构建一个完整的类出来。关键点是需要调用哪些接口,生成哪些指令。
ASM 是基于class字节码指令的,只要将Comparable类的字节码指令 按照ASM的语法翻译一遍就可能通过ASM生成完整的类信息。
通过javap 来查看Comparable.java 的字节码文件
javac -g Comparable.java
javap -v Comparable.class
Classfile /Users/feifei/Desktop/TM/Demo/TrPlugin/asm_demo/src/main/java/com/sogou/iot/asm_demo/Comparable.class
Last modified 2021-1-24; size 468 bytes
MD5 checksum 9fb6157669e4dd90332cb4fd79c0b614
Compiled from "Comparable.java"
class com.sogou.iot.asm_demo.Comparable
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."":()V
#2 = Fieldref #3.#21 // com/sogou/iot/asm_demo/Comparable.LESS:I
#3 = Class #22 // com/sogou/iot/asm_demo/Comparable
#4 = Class #23 // java/lang/Object
#5 = Utf8 LESS
#6 = Utf8 I
#7 = Utf8
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/sogou/iot/asm_demo/Comparable;
#14 = Utf8 compareTo
#15 = Utf8 (Ljava/lang/Object;)I
#16 = Utf8 o
#17 = Utf8 Ljava/lang/Object;
#18 = Utf8 SourceFile
#19 = Utf8 Comparable.java
#20 = NameAndType #7:#8 // "":()V
#21 = NameAndType #5:#6 // LESS:I
#22 = Utf8 com/sogou/iot/asm_demo/Comparable
#23 = Utf8 java/lang/Object
{
int LESS;
descriptor: I
flags:
com.sogou.iot.asm_demo.Comparable();
descriptor: ()V
flags:
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: iconst_m1
6: putfield #2 // Field LESS:I
9: return
LineNumberTable:
line 11: 0
line 13: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/sogou/iot/asm_demo/Comparable;
int compareTo(java.lang.Object);
descriptor: (Ljava/lang/Object;)I
flags:
Code:
stack=1, locals=2, args_size=2
0: iconst_m1
1: ireturn
LineNumberTable:
line 15: 0
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this Lcom/sogou/iot/asm_demo/Comparable;
0 2 1 o Ljava/lang/Object;
}
SourceFile: "Comparable.java"
翻译后的ASM 代码如下(kotlin语言编写)
//生成一个新的类
fun generateClass(out: File) {
var outputStream = FileOutputStream(out)
val cw = ClassWriter(0)
var fv: FieldVisitor
var mv: MethodVisitor
var av0: AnnotationVisitor
cw.visit(
V1_7,
ACC_PUBLIC + ACC_SUPER,
"com/sogou/iot/asm_demo/Comparable",
null,
"java/lang/Object",
null
)
cw.visitSource("Comparable.java", null)
kotlin.run {
fv = cw.visitField(0, "LESS", "I", null, null)
fv.visitEnd()
}
kotlin.run {
mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null)
mv.visitCode()
val l0 = Label()
mv.visitLabel(l0)
mv.visitLineNumber(11, l0)
mv.visitVarInsn(ALOAD, 0)
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false)
val l1 = Label()
mv.visitLabel(l1)
mv.visitLineNumber(12, l1)
mv.visitVarInsn(ALOAD, 0)
mv.visitInsn(ICONST_M1)
mv.visitFieldInsn(PUTFIELD, "com/sogou/iot/asm_demo/Comparable", "LESS", "I")
mv.visitInsn(RETURN)
val l2 = Label()
mv.visitLabel(l2)
mv.visitLocalVariable("this", "Lcom/sogou/iot/asm_demo/Comparable;", null, l0, l2, 0)
mv.visitMaxs(2, 1)
mv.visitEnd()
}
kotlin.run {
mv = cw.visitMethod(0, "compareTo", "(Ljava/lang/Object;)I", null, null)
mv.visitCode()
val l0 = Label()
mv.visitLabel(l0)
mv.visitLineNumber(14, l0)
mv.visitInsn(ICONST_M1)
mv.visitInsn(IRETURN)
val l1 = Label()
mv.visitLabel(l1)
mv.visitLocalVariable("this", "Lcom/sogou/iot/asm_demo/Comparable;", null, l0, l1, 0)
mv.visitLocalVariable("o", "Ljava/lang/Object;", null, l0, l1, 1)
mv.visitMaxs(1, 2)
mv.visitEnd()
}
cw.visitEnd()
val bytes = cw.toByteArray()
outputStream.write(bytes)
outputStream.close()
}
幸运的是 ASM Bytecode Outline工具可以帮助我们方便的查看,java文件编译之后的字节码,以及对应到的ASM指令码.
但ASM Bytecode Outline工具有一个缺点,它无法查看kotlin代码生的字节码和ASM指令。这就尴尬了。
后来诞生了一个新插件ASM Bytecode Viewer Support Kotlin 可以查看Java和Kotlin代码的字节码和ASM指令
使用方式:Code->ASM ByteCode Viewer
直接将dump方法中的代码copy出来,就是ASM生成class文件的指令代码
2.3、如何删除原class中的属性和方法
2.3.1、针对ClassVisitor中无返回值类型的方法,仅保留一个空方法,会去掉生成的类中对应的方法。
public class RemoveDebugAdapter extends ClassVisitor {
public RemoveDebugAdapter(ClassVisitor cv) {
super(ASM4, cv);
}
@Override
public void visitSource(String source, String debug) {
}
@Override
public void visitOuterClass(String owner, String name, String desc) {
}
@Override
public void visitInnerClass(String name, String outerName,
String innerName, int access) {
}
}
For example the following class adapter removes the information about outer and inner classes, as well as the name of the source file from which the class was compiled (the resulting class remains fully functional, because these elements are only used for debugging purposes). This is done by not forwarding anything in the appropriate visit methods:
This strategy does not work for fields and methods, because the visitField and visitMethod methods must return a result. In order to remove a field or method, you must not forward the method call, and return null to the caller
2.3.2、 如果ClassVisitor的回调方法有返回值,那么返回null值,可以去掉生成class中的对应的方法
这里以ComponentManager类为例,演示如何移除其中的toDeleteMethod()方法和toDeleteFiled属性
public class ComponentManager {
public synchronized void initComponet() {
}
public void toDeleteMethod() {
}
public void calculate(){
Long start = System.currentTimeMillis();
int i = 10;
int j = 100+i;
Long end = System.currentTimeMillis();
System.out.println("calucate cost:"+(end-start));
}
public String toDeleteFiled;
}
- 移除类中的方法
//移除原有类中的一个方法
fun scanClassRemoveMethod(file: File, out: File) {
var inputStream = FileInputStream(file)
var outputStream = FileOutputStream(out)
//构造ClassReader对象
var cr = ClassReader(inputStream)
//构造ClassWriter对象
var cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
//构造ClassAdapter对象,指定其传递链的下一级为ClassWriter
var classAdapter =
RemoveMethodClassVisitor(Opcodes.ASM7, cw)
//ClassReader读取类的信息,指定读取到的类信息传递到ClassAdapter
cr.accept(classAdapter, ClassReader.EXPAND_FRAMES)
//最终的类转换成ByteArray
var newClassBytes = cw.toByteArray()
outputStream.write(newClassBytes)
inputStream.close()
outputStream.close()
}
class RemoveMethodClassVisitor(api: Int, classVisitor: ClassVisitor?) :
ClassVisitor(api, classVisitor) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array?
): MethodVisitor? {
System.out.println("visitMethod --- :${name},descriptor:${descriptor}")
if (name?.equals("toDeleteMethod") == true && descriptor.equals("()V") == true) {
return null
} else {
//删除一个方关键的两点:
// (1)不调用 cv.visitMethod(),调用cv.visitMethod()会产生一个MethodVisitor 生成对应的方法
// (2) 不将methodVisitor返回
var mv = cv.visitMethod(access, name, descriptor, signature, exceptions)
return mv
}
}
}
修改后的ComponentManager
public class ComponentManager {
public String toDeleteFiled;
public ComponentManager() {
}
public synchronized void initComponet() {
}
}
- 移除类中的属性
//移除一个属性
fun scanClassRemoveFiled(file: File, out: File) {
var inputStream = FileInputStream(file)
var outputStream = FileOutputStream(out)
var cr = ClassReader(inputStream)
var cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
var classAdapter =
RemoveFiledClassVisitor(Opcodes.ASM7, cw)
cr.accept(classAdapter, ClassReader.EXPAND_FRAMES)
var newClassBytes = cw.toByteArray()
outputStream.write(newClassBytes)
inputStream.close()
outputStream.close()
}
class RemoveFiledClassVisitor(api: Int, classVisitor: ClassVisitor?) :
ClassVisitor(api, classVisitor) {
override fun visitField(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
value: Any?
): FieldVisitor? {
System.out.println("visitField --- access:${access}:${name},descriptor:${descriptor},signature:${signature},value:${value}")
if (name.equals("toDeleteFiled") && descriptor.equals("Ljava/lang/String;")) {
//删除一个属性,只需要特定属性 visitField 返回null即可
return null
} else {
return cv.visitField(access, name, descriptor, signature, value)
}
}
}
修改后的ComponentManager
package com.sogou.iot.trplugin;
public class ComponentManager {
public ComponentManager() {
}
public synchronized void initComponet() {
}
public void toDeleteMethod() {
}
}
2.4、如何新增属性和方法
为已知类新增属性和方法的最好的方式就是在ClassVisitor的visitEnd()方法中,新增visitField()或visitMethod()方法, 但是必须保证没有重复的属性和方法。唯一的方式就是遍历所有的属性(或方法) 然后进行比较。而visitEnd()方法会在所有的方法和属性都被访问时才会回调。同时visitEnd()方法肯定会被回调。所以visitEnd()最适合新增属性和方法
仍然以ComponentManager类为例,为其新增addedFileld属性和addedMethod方法。
2.4.1、 新增属性
//新增一个属性
fun scanClassAddField(file: File, out: File) {
var inputStream = FileInputStream(file)
var outputStream = FileOutputStream(out)
var cr = ClassReader(inputStream)
var cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
var classAdapter =
AddFiledClassVisitor(Opcodes.ASM7, cw)
cr.accept(classAdapter, ClassReader.EXPAND_FRAMES)
var newClassBytes = cw.toByteArray()
outputStream.write(newClassBytes)
inputStream.close()
outputStream.close()
}
class AddFiledClassVisitor(api: Int, classVisitor: ClassVisitor?) :
ClassVisitor(api, classVisitor) {
val toAddFiledName = "addedFileld"
val toAddFiledDes = "Ljava/lang/String;"
var conflict: Boolean = false
override fun visitField(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
value: Any?
): FieldVisitor {
if (name.equals(toAddFiledName) && descriptor.equals(toAddFiledDes)) {
conflict = true
}
return super.visitField(access, name, descriptor, signature, value)
}
//增加一个属性注意两点:
//(1)新增的属性和类中已有的属性不能冲突(如重名等)
//(2)理论上在访问该类的任何时机都可以新增特定属性,但是考虑到(1)新增属性不能和现有属性冲突,所有最好在visitEnd中 排除属性冲突后,执行新增属性的任务
//(3)新增属性只需要调用 cv.visitField()和fv.visitEnd() 就可完整新增一个属性
override fun visitEnd() {
System.out.println("visitEnd ,conflict:${conflict},${Type.getType(Array::class.java)}")
if (conflict == false) {
//生成实例属性
val filedVisitor = cv.visitField(
Opcodes.ACC_PUBLIC,
"addedFileld",
Type.getType(Array::class.java).descriptor,
null,
null
)
//生成静态属性
//val staticfiledVisitor = cv.visitField((Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC),"addedFileld","Ljava/lang/String;",null,null)
filedVisitor.visitEnd()
}
super.visitEnd()
}
}
修改前的ComponentManager
package com.sogou.iot.trplugin;
public class ComponentManager {
public String toDeleteFiled;
public ComponentManager() {
}
public synchronized void initComponet() {
}
public void toDeleteMethod() {
}
}
修改后的ComponentManager类
public class ComponentManager {
public String toDeleteFiled;
public String[] addedFileld;
public ComponentManager() {
}
public synchronized void initComponet() {
}
public void toDeleteMethod() {
}
}
2.4.2、 新增方法
//新增方法
fun scanClassAddMethod(file: File, out: File) {
var inputStream = FileInputStream(file)
var outputStream = FileOutputStream(out)
var cr = ClassReader(inputStream)
var cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
var classAdapter =
AddMethodClassVisitor(Opcodes.ASM7, cw)
cr.accept(classAdapter, ClassReader.EXPAND_FRAMES)
var newClassBytes = cw.toByteArray()
outputStream.write(newClassBytes)
inputStream.close()
outputStream.close()
}
//增加方法
class AddMethodClassVisitor(api: Int, classVisitor: ClassVisitor?) :
ClassVisitor(api, classVisitor) {
val toAddMethodName = "addedMethod"
val toAddMethodDescriptor = "()V"
var confict = false
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array?
): MethodVisitor {
if (name.equals(toAddMethodName) && descriptor.equals(toAddMethodDescriptor)) {
confict = true
}
return super.visitMethod(access, name, descriptor, signature, exceptions)
}
override fun visitEnd() {
if (confict == false) {
var methodVisitor = cv.visitMethod(
Opcodes.ACC_PUBLIC or Opcodes.ACC_SYNCHRONIZED,
toAddMethodName,
toAddMethodDescriptor,
null,
null
)
methodVisitor.visitCode()
//...此处增加方法的具体实现
methodVisitor.visitInsn(Opcodes.RETURN)
methodVisitor.visitMaxs(0, 1)
methodVisitor.visitEnd()
}
super.visitEnd()
}
}
修改前的ComponentManager
package com.sogou.iot.trplugin;
public class ComponentManager {
public String toDeleteFiled;
public ComponentManager() {
}
public synchronized void initComponet() {
}
public void toDeleteMethod() {
}
}
修改后的ComponentManager
package com.sogou.iot.trplugin;
public class ComponentManager {
public String toDeleteFiled;
public ComponentManager() {
}
public synchronized void initComponet() {
}
public void toDeleteMethod() {
}
}
2.5、如何修改原有类的特定方法
原始类信息
class TestCost {
public void haveATry() {
int i = 0;
int j = i + 10;
System.out.println("haveATry j:" + j);
}
}
修改目标:修改HaveATry方法,在方法执行前和执行后分别一行代码,以达到统计方法执行时长的作用。
2.5.1、方案一
在haveATry()方法开始后和结束前 分别利用临时变量记录当前时间start和end,最后利用end-start 计算方法耗时
class TestCost {
public void haveATry() {
//插入一
Long start = System.currentTimeMillis();
int i = 0;
int j = i + 10;
System.out.println("haveATry j:" + j);
//插入二
Long end = System.currentTimeMillis();
System.out.println("calucate cost:" + (end - start));
}
}
fun scanClassModifyMethod(file: File, out: File) {
var inputStream = FileInputStream(file)
var outputStream = FileOutputStream(out)
var cr = ClassReader(inputStream)
var cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
var classAdapter =
ModifyMethodClassVisitor(Opcodes.ASM7, cw)
cr.accept(classAdapter, ClassReader.EXPAND_FRAMES)
var newClassBytes = cw.toByteArray()
outputStream.write(newClassBytes)
inputStream.close()
outputStream.close()
}
class ModifyMethodClassVisitor(api: Int, classVisitor: ClassVisitor?) :
ClassVisitor(api, classVisitor) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array?
): MethodVisitor {
if (name.equals("haveATry")) {
val mv = cv.visitMethod(access, name, descriptor, signature, exceptions)
return ScanMethodvisitor(api, mv, access, name, descriptor)
} else {
return cv.visitMethod(access, name, descriptor, signature, exceptions)
}
}
}
class ScanMethodvisitor(
api: Int,
methodVisitor: MethodVisitor?,
access: Int,
name: String?,
descriptor: String?
) : AdviceAdapter(api, methodVisitor, access, name, descriptor) {
override fun onMethodEnter() {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
//注意这里新增的局部变量 不能和方法原有的局部变量有冲突
mv.visitVarInsn(ASTORE, 10)
super.onMethodEnter()
}
override fun onMethodExit(opcode: Int) {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
mv.visitVarInsn(ASTORE, 11)
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false);
mv.visitLdcInsn("calucate cost:");
mv.visitMethodInsn(
INVOKEVIRTUAL,
"java/lang/StringBuilder",
"append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;",
false
);
mv.visitVarInsn(ALOAD, 11);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
mv.visitVarInsn(ALOAD, 10);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
mv.visitInsn(LSUB);
mv.visitMethodInsn(
INVOKEVIRTUAL,
"java/lang/StringBuilder",
"append",
"(J)Ljava/lang/StringBuilder;",
false
);
mv.visitMethodInsn(
INVOKEVIRTUAL,
"java/lang/StringBuilder",
"toString",
"()Ljava/lang/String;",
false
);
mv.visitMethodInsn(
INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V",
false
);
super.onMethodExit(opcode)
}
}
修改后的TestCost如下:
package com.sogou.iot.trplugin;
import com.sogou.iot.annotations.Cost;
class TestCost {
TestCost() {
}
@Cost
public void haveATry() {
Long var10 = System.currentTimeMillis();
int i = 0;
int j = i + 10;
System.out.println("haveATry j:" + j);
Long var11 = System.currentTimeMillis();
System.out.println("calucate cost:" + (var11 - var10));
}
}
2.5.2、 隐患
方案一中统计方法耗时,在haveATry()中新增了两个局部变量start和end。这是对原有的局部变量表有干扰的,容易和原有类的局部变量表差生冲突覆盖等,使最终生成的class产生错误。
所以修改类中的方法,应该做到尽量不影响原方法栈的局部变量表,以减少错误发生。
所以为达到统计方法耗时的目的,可以参照方案二:
- (1)新建一个辅助类TimeCostCache,用于记录方法进入和退出的时间点
public class TimeCostCache {
public static HashMap times = new HashMap<>();
public static void enterInMethod(String methodName) {
times.put(methodName, System.currentTimeMillis());
}
public static void enterOutMethod(String methodName){
long start = times.get(methodName);
long end = System.currentTimeMillis();
System.out.println("TimeCostCache --> "+methodName+" coast:"+(end-start)+"ms");
}
}
- (2)TestCost改为如下形式,规避对原有方法栈局部变量表产生的干扰。
class TestCost {
public void haveATry() {
//插入一
TimeCostCache.enterInMethod("haveATry");
int i = 0;
int j = i + 10;
System.out.println("haveATry j:" + j);
//插入二
TimeCostCache.enterOutMethod("haveATry");
}
}
2.6、工具类
2.6.1、 Type
Type 对象代表一个java 类,可以从一个Type 描述或者Class 进行构造.
利用Type类型可以帮助我们生成descriptor,通过descriptor提取方法的参数和返回值。
- 对于原始数据类型,Type中进行了内置,如 Type.INT
- 对于非原始数据类型,可以通过Type.getType()构造Type
String类型的Type
Type.getType(String.class)
构造Array类型的Type
Type.getType(Array::class.java)
- getInternalName(),可以Type实际代表的Java类
Type.getType(String.class).getInternalName() gives the internal
name of the String class, i.e. "java/lang/String".
- getDescriptor().
instead of using "Ljava/lang/String;" ," in your code you could use Type.getType(String.class).getDescriptor()
- getArgumentTypes 和 getReturnType
the getArgumentTypes and getReturnType methods
can be used to get the Type objects corresponding to the argument types and return types of a method.
Type.getArgumentTypes("(I)V") returns an array containing the single element Type.INT_TYPE.
. Similarly, a call
to Type.getReturnType("(I)V") returns the Type.VOID_TYPE object.
2.6.2、 AdviceAdapter
dviceAdapter 是一个抽象方法,用于帮助我们在一个方法前 和RETRUN前 插入代码。
class AddTimerMethodAdapter6 extends AdviceAdapter {
public AddTimerMethodAdapter6(int access, String name, String desc,
MethodVisitor mv) {
super(ASM4, mv, access, name, desc);
}
@Override protected void onMethodEnter() {
mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",
"currentTimeMillis", "()J");
mv.visitInsn(LSUB);
mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
}
@Override protected void onMethodExit(int opcode) {
mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",
"currentTimeMillis", "()J");
mv.visitInsn(LADD);
mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
}
@Override public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack + 4, maxLocals);
}
}
2.6.3、ASMifier
ASMifier 允许我们打印生成某种Class的ASM 的源码。 例如,我们不知道如何用ASM 生成一个class文件,我们可以这样做: 首先编写该类的java源码,然后用javac编译,最后使用ASMifier 访问编译后的class. 你将会得到生成该java类的asm源码
The ASMifier class can be used from the command line. For example using:
java -classpath asm.jar:asm-util.jar \
org.objectweb.asm.util.ASMifier \
java.lang.Runnable
Gradle插件ASM Bytecode Viewer Support Kotlin 提供了ASMifier的可视化操作,使用起来更方便。
2.7、示例代码
示例代码 可参考
trplugin asm_demo 模块,AsmPractice.kt
三、参考文章
https://asm.ow2.io/asm4-guide.pdf