package org.example.asm8.printArgs;
import java.util.Date;
public class Hello {
private String name;
private int age;
private Date birthDate;
public Hello(String name, int age, Date birthDate) {
this.name = name;
this.age = age;
this.birthDate = birthDate;
}
public int hello() {
if (age < 18) {
throw new RuntimeException("too young");
}
return age;
}
public String testHello() {
hello();
return "success!";
}
}
package org.example.asm8.printArgs;
import java.util.Date;
public class Hello {
private String name;
private int age;
private Date birthDate;
public Hello(String name, int age, Date birthDate) {
long var6 = System.currentTimeMillis();
PrintUtils.printText("Enter: 方法名 ,方法描述符 (Ljava/lang/String;ILjava/util/Date;)V" );
PrintUtils.printObject("入参类型:", name);
PrintUtils.printObject("入参类型:", new Integer(age));
PrintUtils.printObject("入参类型:", birthDate);
this.name = name;
this.age = age;
this.birthDate = birthDate;
PrintUtils.printText("Exit: 方法名 ,方法描述符 (Ljava/lang/String;ILjava/util/Date;)V" );
PrintUtils.printObject("返回值类型:", "void方法,没有返回值");
PrintUtils.printSpendTime("" , var6);
}
public int hello() {
long var3 = System.currentTimeMillis();
PrintUtils.printText("Enter: 方法名 hello,方法描述符 ()I");
if (this.age < 18) {
RuntimeException var5 = new RuntimeException("too young");
PrintUtils.printText("Exit: 方法名 hello,方法描述符 ()I");
PrintUtils.printObject("返回值类型:", "有异常抛出了");
PrintUtils.printSpendTime("hello", var3);
throw var5;
} else {
int var10000 = this.age;
PrintUtils.printText("Exit: 方法名 hello,方法描述符 ()I");
PrintUtils.printObject("返回值类型:", new Integer(var10000));
PrintUtils.printSpendTime("hello", var3);
return var10000;
}
}
public String testHello() {
long var3 = System.currentTimeMillis();
PrintUtils.printText("Enter: 方法名 testHello,方法描述符 ()Ljava/lang/String;");
this.hello();
PrintUtils.printText("Exit: 方法名 testHello,方法描述符 ()Ljava/lang/String;");
PrintUtils.printObject("返回值类型:", "success!");
PrintUtils.printSpendTime("testHello", var3);
return "success!";
}
}
package org.example.asm8.printArgs;
public class PrintUtils {
public static void printText(String str) {
System.out.println(str);
}
public static void printObject(String name, Object value) {
if (value == null) {
System.out.println("null");
} else {
System.out.println(name + value.getClass().getSimpleName() + ",参数值:" + value);
}
}
public static void printSpendTime(String methodName, long startTime) {
System.out.println(methodName + " 耗时:" + (System.currentTimeMillis() - startTime) + " ms");
System.out.println("*************************************************");
}
}
package org.example.asm8.printArgs;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;
import java.io.FileOutputStream;
public class PrintClassArgs implements Opcodes {
public static void main(String[] args) throws Exception {
ClassReader cr = new ClassReader(Hello.class.getName());
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
cr.accept(new MyClassVisitor(ASM9, cw), ClassReader.EXPAND_FRAMES);
byte[] bytes = cw.toByteArray();
// 生成class
String path = PrintClassArgs.class.getResource("/").getPath() + "org/example/asm8/printArgs/Hello.class";
System.out.println("输出路径:" + path);
try (FileOutputStream fos = new FileOutputStream(path)) {
fos.write(bytes);
}
}
static class MyClassVisitor extends ClassVisitor {
protected MyClassVisitor(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
if (methodVisitor != null) {
// 排除抽象方法和本地方法
boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
boolean isNativeMethod = (access & ACC_NATIVE) != 0;
if (!isAbstractMethod && !isNativeMethod) {
methodVisitor = new MyMethodVisitor(api, methodVisitor, access, name, descriptor);
}
}
return methodVisitor;
}
}
static class MyMethodVisitor extends AdviceAdapter {
String str = "方法名 " + super.getName() + ",方法描述符 " + super.methodDesc;
// 开始时间在局部变量表中的位置
int start = 0;
protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
super(api, methodVisitor, access, name, descriptor);
}
@Override
protected void onMethodEnter() {
// 记录开始时间
super.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
// 在局部变量表中nextLocal位置存放long类型的数值,nextLocal表示当前已存数据的下一个位置的索引
// 下面执行完visitVarInsn(LSTORE, start)后,nextLocal会根据存入的数据类型长度,后移一位或两位
start = nextLocal;
// 将栈顶的数值放入局部变量表中start位置
super.visitVarInsn(LSTORE, start);
// 进入方法时,先打印一句话 Enter: xxx
printText("Enter: " + str);
// 取出方法所有的入参类型
Type[] argumentTypes = getArgumentTypes();
for (int i = 0; i < argumentTypes.length; i++) {
Type argumentType = argumentTypes[i];
// 将方法的入参从局部变量表中取出,压入到操作数栈中
loadArg(i);
// 对操作数栈顶的数据按照argumentType类型进行包装,并用包装好的值替换原来栈顶的这个数值,而且数据类型也是一致的
box(argumentType);
// 打印操作数栈顶的这个值,就实现了对方法入参的循环打印
printObject("入参类型:");
}
}
@Override
protected void onMethodExit(int opcode) {
// 退出方法时,打印一句话 Exit: xxx
printText("Exit: " + str);
// throw 与 return 指令没有返回值,这里手动将希望打印到控制台的字符串压入到操作数栈顶
if (opcode == ATHROW) {
super.visitLdcInsn("有异常抛出了");
} else if (opcode == RETURN) {
super.visitLdcInsn("void方法,没有返回值");
} else if (opcode == ARETURN) {
// 复制操作数栈顶的1个数值,并将复制结果压入操作数栈顶,此时操作数栈上有2个连续相同的数值
// 复制的目的是,多出来的这个数值用来打印到控制台,原来栈顶的数值不受影响
dup();
} else if (opcode == LRETURN || opcode == DRETURN) {
// 因为double和long类型(64bit)占2个slot,所以要复制操作数栈顶的2个数值,并将其压入操作数栈顶
dup2();
// 对栈顶的数据按照返回值类型进行包装,并用包装好的值替换原来栈顶的这个数值
// double类型会用Double.valueOf()进行包装,long类型会用Long.valueOf()进行包装
box(getReturnType());
} else {
dup();
// 这里排除上面几种返回值类型,这里的opcode应该是 FRETURN 和 IRETURN
// 对相应类型的数据进行Float.valueOf()或者Integer.valueOf()包装
box(getReturnType());
}
// 因为这里打印时,需要参数是Object类型,所以上面的2个box(getReturnType())必须有,目的是将基本数据类型转成包装类
// 否则打印时,传的是基本数据类型,不是Object一定会报错
// 前面2个if没有返回值,所以不需要按照返回值数据类型进行包装,直接传入String类型数据给printObject方法进行打印
// 第3个if是Object类型返回值,复制一份压到栈顶即可,不需要再包装了
printObject("返回值类型:");
// 打印耗时
printSpendTime();
}
private void printText(String str) {
// 将str从常量池中取出,压入操作数栈顶
super.visitLdcInsn(str);
// 从操作数栈顶取出一个数据,作为入参调用PrintUtils的public static void printText(String str)方法
super.visitMethodInsn(INVOKESTATIC, Type.getInternalName(PrintUtils.class), "printText",
"(Ljava/lang/String;)V", false);
}
private void printObject(String name) {
// 将name压入栈顶
super.visitLdcInsn(name);
// printObject方法入参是name和value,从栈顶取参数时,从后往前输入
// 所以要先拿到Object类型的value再拿String类型的name,但此时栈顶是name,name下面是value的包装类
// 所以要调用swap方法,将栈顶最顶端的两个数值互换(数值不能是long或double类型)
swap();
// 从操作数栈顶取出一个数据,作为入参调用PrintUtils的public static void printObject(String name, Object value)方法
super.visitMethodInsn(INVOKESTATIC, Type.getInternalName(PrintUtils.class), "printObject",
"(Ljava/lang/String;Ljava/lang/Object;)V", false);
}
private void printSpendTime() {
// 方法名压入栈顶
super.visitLdcInsn(super.getName());
// 将开始时间从局部变量表start位置压入栈顶
super.visitVarInsn(LLOAD, start);
super.visitMethodInsn(INVOKESTATIC, Type.getInternalName(PrintUtils.class), "printSpendTime",
"(Ljava/lang/String;J)V", false);
}
}
}
package org.example.asm8.printArgs;
import java.util.Date;
public class HelloRun {
public static void main(String[] args) {
Hello instance = new Hello("Fisher", 18, new Date());
System.out.println(instance.testHello());
}
}
Enter: 方法名 ,方法描述符 (Ljava/lang/String;ILjava/util/Date;)V
入参类型:String,参数值:Fisher
入参类型:Integer,参数值:18
入参类型:Date,参数值:Wed Aug 17 10:04:08 CST 2022
Exit: 方法名 ,方法描述符 (Ljava/lang/String;ILjava/util/Date;)V
返回值类型:String,参数值:void方法,没有返回值
耗时:26 ms
*************************************************
Enter: 方法名 testHello,方法描述符 ()Ljava/lang/String;
Enter: 方法名 hello,方法描述符 ()I
Exit: 方法名 hello,方法描述符 ()I
返回值类型:Integer,参数值:18
hello 耗时:0 ms
*************************************************
Exit: 方法名 testHello,方法描述符 ()Ljava/lang/String;
返回值类型:String,参数值:success!
testHello 耗时:0 ms
*************************************************
success!
Type boxedType = getBoxedType(type);
newInstance(boxedType);
if (type.getSize() == 2) {
// Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o
dupX2();
dupX2();
pop();
} else {
// p -> po -> opo -> oop -> o
dupX1();
swap();
}
invokeConstructor(boxedType, new Method("" , Type.VOID_TYPE, new Type[] {type}));