3.6 執(zhí)行InjectUnitTest.java的test()方法查看InjectTest.class結(jié)果
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.xyaty.asmdemo;
public class InjectTest {
public InjectTest() {
long var1 = System.currentTimeMillis();
long var3 = System.currentTimeMillis();
System.out.println("execute: " + (var3 - var1) + "ms");
}
public static void main(String[] var0) throws InterruptedException {
long var1 = System.currentTimeMillis();
Thread.sleep(1000L);
long var3 = System.currentTimeMillis();
System.out.println("execute: " + (var3 - var1) + "ms");
}
public void methodA() {
long var1 = System.currentTimeMillis();
System.out.println("methodA");
long var3 = System.currentTimeMillis();
System.out.println("execute: " + (var3 - var1) + "ms");
}
}
發(fā)現(xiàn)在每個方法中都加入了
long var1 = System.currentTimeMillis();
和
long var3 = System.currentTimeMillis();
System.out.println("execute: " + (var3 - var1) + "ms");
如果僅僅在main()方法才注入代碼荡碾,就需要引入自定義注解來標(biāo)記指定的方法
四谨读、引入自定義注解,標(biāo)記方法才注入代碼
4.1 注解類ASMTest.java坛吁,并通過javac編譯成ASMTest.class(略)
package com.xyaty.asmdemo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* DESC :
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface ASMTest {
}
4.2 在InjectTest.java的main()方法上加上注解@ASMTest劳殖,標(biāo)記此方法铐尚,并通過javac生成class
package com.xyaty.asmdemo;
/**
* DESC :
*/
public class InjectTest {
@ASMTest
public static void main(String[] args) throws InterruptedException {
Thread.sleep(1000);
}
public void methodA() {
System.out.println("methodA");
}
}
4.3 在上述MyMethodVisitor的下列方法中加入注解判斷
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
System.out.println("visitAnnotation===>methodName="+getName()+", descriptor="+descriptor);
//如果方法的注解名字是@ASMTest,則給此方法注入代碼
if ("Lcom/xyaty/asmdemo/ASMTest;".equals(descriptor)) {
isInject = true;
} else {
isInject = false;
}
return super.visitAnnotation(descriptor, visible);
}
/**
* 進(jìn)入方法插入內(nèi)容
*/
@Override
protected void onMethodEnter() {
super.onMethodEnter();
if (!isInject) {
return;
}
}
@Override
protected void onMethodExit(int opcode) {
super.onMethodExit(opcode);
if (!isInject) {
return;
}
}
InjectUnitTest.java完整代碼如下:
package com.xyaty.asmdemo;
import org.junit.Test;
import org.objectweb.asm.AnnotationVisitor;
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 org.objectweb.asm.commons.Method;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* DESC :
*/
public class InjectUnitTest {
/**
* 單元測試方法哆姻,右擊test()方法宣增,選擇run test()方法即可查看結(jié)果
*/
@Test
public void test() {
try {
//讀取待插樁的class
FileInputStream fis = new FileInputStream(
new File("src/test/java/com/xyaty/asmdemo/InjectTest.class"));
/**
* 執(zhí)行分析與插樁
* ClassReader是class字節(jié)碼的讀取與分析引擎
*/
ClassReader classReader = new ClassReader(fis);
// ClassWriter寫出器, COMPUTE_FRAMES表示自動計算棧幀和局部變量表的大小
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
/**
* 執(zhí)行分析矛缨,處理結(jié)果寫入classWriter, EXPAND_FRAMES表示棧圖以擴(kuò)展格式進(jìn)行訪問
* 執(zhí)行插樁的代碼就在MyClassVisitor中實現(xiàn)
*/
classReader.accept(new MyClassVisitor(Opcodes.ASM9, classWriter), ClassReader.EXPAND_FRAMES);
//獲得執(zhí)行了插樁之后的字節(jié)碼數(shù)據(jù)
byte[] bytes = classWriter.toByteArray();
// 重新寫入InjectTest.class中(也可以寫入到其他class中箕昭,InjectTest1.class)灵妨,完成插樁
FileOutputStream fos = new FileOutputStream(
new File("src/test/java/com/xyaty/asmdemo/InjectTest.class"));
fos.write(bytes);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
System.out.println("visitMethod==>name="+name);
/**
* 會輸出以下方法:
* visitMethod==>name=<init>
* visitMethod==>name=main
*/
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
return new MyMethodVisitor(api, methodVisitor, access, name,descriptor);
}
}
/**
* 之所以繼承自AdviceAdapter,是因為AdviceAdapter是MethodVisitor的子類盟广,
* AdviceAdapter封裝了指令插入方法闷串,更為直觀與簡單,
* 要使用其中的onMethodEnter和 onMethodExit方法進(jìn)行字節(jié)碼插樁筋量,
*
* 繼承關(guān)系如下:
* AdviceAdapter extends GeneratorAdapter
* GeneratorAdapter extends LocalVariablesSorter
* LocalVariablesSorter extends MethodVisitor
*/
public class MyMethodVisitor extends AdviceAdapter {
long start;
private int startIdentifier;
private boolean isInject = false;//是否注入代碼
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
System.out.println("visitAnnotation===>methodName="+getName()+", descriptor="+descriptor);
//如果方法的注解名字是@ASMTest烹吵,則給此方法注入代碼
if ("Lcom/xyaty/asmdemo/ASMTest;".equals(descriptor)) {
isInject = true;
} else {
isInject = false;
}
return super.visitAnnotation(descriptor, visible);
}
protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
super(api, methodVisitor, access, name, descriptor);
}
/**
* 進(jìn)入方法插入內(nèi)容
*/
@Override
protected void onMethodEnter() {
super.onMethodEnter();
if (!isInject) {
return;
}
// start = System.currentTimeMillis();
/**
* @Type owner 調(diào)用哪個類
* @Method method 調(diào)用某個類的靜態(tài)方法(參數(shù)name: 方法名字,descriptor:方法中參數(shù)和方法返回值類型)
*/
invokeStatic(Type.getType("Ljava/lang/System;"), new Method("currentTimeMillis", "()J"));
//調(diào)用newLocal創(chuàng)建一個long類型的變量桨武,返回一個int類型索引identifier
startIdentifier = newLocal(Type.LONG_TYPE);
//保存到本地變量索引中肋拔,用一個本地變量接收上一步執(zhí)行的結(jié)果
storeLocal(startIdentifier);
}
/**
* 在方法結(jié)尾插入內(nèi)容
* @param opcode
*/
@Override
protected void onMethodExit(int opcode) {
super.onMethodExit(opcode);
if (!isInject) {
return;
}
// long end = System.currentTimeMillis();
// System.out.println("execute: "+(end - start)+"ms");
invokeStatic(Type.getType("Ljava/lang/System;"), new Method("currentTimeMillis", "()J"));
//調(diào)用newLocal創(chuàng)建一個long類型的變量,返回一個int類型索引identifier
int endIdentifier = newLocal(Type.LONG_TYPE);
//保存到本地變量索引中呀酸,用一個本地變量接收上一步執(zhí)行的結(jié)果
storeLocal(endIdentifier);
//獲取System的靜態(tài)字段out,類型為PrintStream
getStatic(Type.getType("Ljava/lang/System;"),
"out", Type.getType("Ljava/io/PrintStream;"));
/**
* "execute: "+(end - start)+"ms"實際是內(nèi)部創(chuàng)建StringBuilder來拼接
* 源碼:NEW java/lang/StringBuilder
* 創(chuàng)建一個對象StringBuilder
*/
newInstance(Type.getType("Ljava/lang/StringBuilder;"));
// dup壓入棧頂凉蜂,讓下面的INVOKESPECIAL 知道執(zhí)行誰的構(gòu)造方法創(chuàng)建StringBuilder
dup();
/**
* 源碼:INVOKESPECIAL java/lang/StringBuilder.<init> ()V
* 創(chuàng)建StringBuilder的構(gòu)造方法,用init來代替
*/
invokeConstructor(Type.getType("Ljava/lang/StringBuilder;"),
new Method("<init>", "()V"));
visitLdcInsn("execute: ");
/**
* 源碼:INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
* 調(diào)用append方法
*/
invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"),
new Method("append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"));
/**
* 對結(jié)束時間和開始時間進(jìn)行減法操作
* LLOAD 3 先加載結(jié)束時間
* LLOAD 1 后加載開始時間
* LSUB 執(zhí)行減法操作
*/
loadLocal(endIdentifier);
loadLocal(startIdentifier);
//執(zhí)行減法操作性誉,返回long類型
math(SUB, Type.LONG_TYPE);
/**
* 源碼:INVOKEVIRTUAL java/lang/StringBuilder.append (J)Ljava/lang/StringBuilder;
* LDC "ms"
*/
invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"),
new Method("append", "(J)Ljava/lang/StringBuilder;"));
//拼接毫秒
visitLdcInsn("ms");
/**
* 源碼:
* INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
* INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
* INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
*/
invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"),
new Method("append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"));
invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"),
new Method("toString", "()Ljava/lang/String;"));
invokeVirtual(Type.getType("Ljava/io/PrintStream;"),
new Method("println", "(Ljava/lang/String;)V"));
}
}
}
再次運行InjectTest.class結(jié)果如下窿吩,可以看到只有main()方法注入了代碼
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.xyaty.asmdemo;
public class InjectTest {
public InjectTest() {
}
@ASMTest
public static void main(String[] var0) throws InterruptedException {
long var1 = System.currentTimeMillis();
Thread.sleep(1000L);
long var3 = System.currentTimeMillis();
System.out.println("execute: " + (var3 - var1) + "ms");
}
public void methodA() {
System.out.println("methodA");
}
}
到此結(jié)束。
參考文章:
https://blog.csdn.net/zenmela2011/article/details/125586333
https://blog.csdn.net/huangbin123/article/details/123322667