由于内容篇幅限定,紧接着上篇
https://www.jianshu.com/p/c975081b43fd?u_atoken=de4d112a-6864-4703-ad69-f82922d505de&u_asession=01X_C9UMmFccLMXpIfjRM8FYXsq1g_CrgffyPiHHUpgZGBrWiw193wtdhvbyaJ4chEX0KNBwm7Lovlpxjd_P_q4JsKWYrT3W_NKPr8w6oU7K_OI3-HIFVcmZbMNunYRvYCslvTX-jMTLEIhdGFg3rxgWBkFo3NEHBv0PZUm6pbxQU&u_asig=05e9XZPq2Q7yJkKdk86YtiyFfiNPAdPSg-Voed-oPaPvY2QwBnCAT6a9bvIxBzozUzQDvsYuznxmEgCniYpEFR8BE5VfTwKn3vCIpIwgp7gRXcA22dI9OX-SjB0FJAYc2ww3dPL_NNCbygKg7jU_P9L1CtmgFQaXXOFbG5UybdQYj9JS7q8ZD7Xtz2Ly-b0kmuyAKRFSVJkkdwVUnyHAIJzet9GpoGT6ubHEJ3SNQzl3mpaIfenFB8MFcEbsDwJo4a6FPw117USKdEPc8n7HkzU-3h9VXwMyh6PgyDIVSG1W-IddwkY7v-CAh1IBL0sYO20tCNasH120WVmgCT0pzm_wUd5n3s6RPzq9rJ9ybbMWH4abeDNZysnYRhxU5ybSOYmWspDxyAEEo4kbsryBKb9Q&u_aref=%2FnUp0P8E0Oe%2BxeO5Bz5i0j215Eo%3D
3.6 实行InjectUnitTest.java的test()方法检察InjectTest.class效果
//// 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"); }}发现在每个方法中都到场了
long var1 = System.currentTimeMillis();和long var3 = System.currentTimeMillis();System.out.println("execute: " + (var3 - var1) + "ms");如果仅仅在main()方法才注入代码,就需要引入自界说注解来标记指定的方法
四、引入自界说注解,标记方法才注入代码
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,标记此方法,并通过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); } /** * 进入方法插入内容 */ @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()方法即可检察效果 */ @Test public void test() { try { //读取待插桩的class FileInputStream fis = new FileInputStream( new File("src/test/java/com/xyaty/asmdemo/InjectTest.class")); /** * 实行分析与插桩 * ClassReader是class字节码的读取与分析引擎 */ ClassReader classReader = new ClassReader(fis); // ClassWriter写出器, COMPUTE_FRAMES表现主动盘算栈帧和局部变量表的巨细 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); /** * 实行分析,处置处罚效果写入classWriter, EXPAND_FRAMES表现栈图以扩展格式举行访问 * 实行插桩的代码就在MyClassVisitor中实现 */ classReader.accept(new MyClassVisitor(Opcodes.ASM9, classWriter), ClassReader.EXPAND_FRAMES); //得到实行了插桩之后的字节码数据 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方法举行字节码插桩, * * 继承关系如下: * 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); } /** * 进入方法插入内容 */ @Override protected void onMethodEnter() { super.onMethodEnter(); if (!isInject) { return; }// start = System.currentTimeMillis(); /** * @Type owner 调用哪个类 * @Method method 调用某个类的静态方法(参数name: 方法名字,descriptor:方法中参数和方法返回值范例) */ invokeStatic(Type.getType("Ljava/lang/System;"), new Method("currentTimeMillis", "()J")); //调用newLocal创建一个long范例的变量,返回一个int范例索引identifier startIdentifier = newLocal(Type.LONG_TYPE); //保存到当地变量索引中,用一个当地变量吸取上一步实行的效果 storeLocal(startIdentifier); } /** * 在方法末端插入内容 * @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")); //调用newLocal创建一个long范例的变量,返回一个int范例索引identifier int endIdentifier = newLocal(Type.LONG_TYPE); //保存到当地变量索引中,用一个当地变量吸取上一步实行的效果 storeLocal(endIdentifier); //获取System的静态字段out,范例为PrintStream getStatic(Type.getType("Ljava/lang/System;"), "out", Type.getType("Ljava/io/PrintStream;")); /** * "execute: "+(end - start)+"ms"实际是内部创建StringBuilder来拼接 * 源码:NEW java/lang/StringBuilder * 创建一个对象StringBuilder */ newInstance(Type.getType("Ljava/lang/StringBuilder;")); // dup压入栈顶,让下面的INVOKESPECIAL 知道实行谁的构造方法创建StringBuilder dup(); /** * 源码:INVOKESPECIAL java/lang/StringBuilder.<init> ()V * 创建StringBuilder的构造方法,用init来取代 */ invokeConstructor(Type.getType("Ljava/lang/StringBuilder;"), new Method("<init>", "()V")); visitLdcInsn("execute: "); /** * 源码:INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; * 调用append方法 */ invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"), new Method("append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;")); /** * 对竣事时间和开始时间举行减法操纵 * LLOAD 3 先加载竣事时间 * LLOAD 1 后加载开始时间 * LSUB 实行减法操纵 */ loadLocal(endIdentifier); loadLocal(startIdentifier); //实行减法操纵,返回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效果如下,可以看到只有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"); }}到此竣事。
参考文章:
https://blog.csdn.net/zenmela2011/article/details/125586333
https://blog.csdn.net/huangbin123/article/details/123322667 |