编程语言
首页 > 编程语言> > Java ASM系列:(037)AdviceAdapter介绍

Java ASM系列:(037)AdviceAdapter介绍

作者:互联网

本文属于[Java ASM系列一:Core API](https://blog.51cto.com/lsieun/2924583)当中的一篇。 对于`AdviceAdapter`类来说,能够很容易的实现在“方法进入”和“方法退出”时添加代码。`AdviceAdapter`类的特点:引入了`onMethodEnter()`方法和`onMethodExit()`方法。 ## 1. AdviceAdapter类 ### 1.1 class info 第一个部分,`AdviceAdapter`类是一个抽象的(`abstract`)、特殊的`MethodVisitor`类。 具体来说,`AdviceAdapter`类继承自`GeneratorAdapter`类,而`GeneratorAdapter`类继承自`LocalVariablesSorter`类,`LocalVariablesSorter`类继承自`MethodVisitor`类。 - org.objectweb.asm.MethodVisitor - org.objectweb.asm.commons.LocalVariablesSorter - org.objectweb.asm.commons.GeneratorAdapter - org.objectweb.asm.commons.AdviceAdapter 由于`AdviceAdapter`类是抽象类(`abstract`),如果我们想使用这个类,那么就需要实现一个具体的子类。 ```java public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes { } ``` ### 1.2 fields 第二个部分,`AdviceAdapter`类定义的字段有哪些。其中,`isConstructor`字段是判断当前方法是不是构造方法。如果当前方法是构造方法,在“方法进入”时添加代码,需要特殊处理。 ```java public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes { protected int methodAccess; protected String methodDesc; private final boolean isConstructor; } ``` ### 1.3 constructors 第三个部分,`AdviceAdapter`类定义的构造方法有哪些。需要注意的是,`AdviceAdapter`的构造方法是用`protected`修饰,因此这个构造方法只能在子类当中访问。换句话说,在外界不能用`new`关键字来创建对象。 ```java public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes { protected AdviceAdapter(final int api, final MethodVisitor methodVisitor, final int access, final String name, final String descriptor) { super(api, methodVisitor, access, name, descriptor); methodAccess = access; methodDesc = descriptor; isConstructor = "".equals(name); } } ``` ### 1.4 methods 第四个部分,`AdviceAdapter`类定义的方法有哪些。 在`AdviceAdapter`类的方法中,定义了两个重要的方法:`onMethodEnter()`方法和`onMethodExit()`方法。 ```java public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes { // Generates the "before" advice for the visited method. // The default implementation of this method does nothing. // Subclasses can use or change all the local variables, but should not change state of the stack. // This method is called at the beginning of the method or // after super class constructor has been called (in constructors). protected void onMethodEnter() {} // Generates the "after" advice for the visited method. // The default implementation of this method does nothing. // Subclasses can use or change all the local variables, but should not change state of the stack. // This method is called at the end of the method, just before return and athrow instructions. // The top element on the stack contains the return value or the exception instance. protected void onMethodExit(final int opcode) {} } ``` 对于`onMethodEnter()`和`onMethodExit()`这两个方法,我们从三个角度来把握它们: - 第一个角度,应用场景。 - `onMethodEnter()`方法:在“方法进入”的时候,添加一些代码逻辑。 - `onMethodExit()`方法:在“方法退出”的时候,添加一些代码逻辑。 - 第二个角度,注意事项。 - 第一点,对于`onMethodEnter()`和`onMethodExit()`这两个方法,都要注意Subclasses can use or change all the local variables, but should not change state of the stack。也就是说,要保持operand stack在修改前和修改后是一致的。 - 第二点,对于`onMethodExit()`方法,要注意The top element on the stack contains the return value or the exception instance。也就是说,“方法退出”的时候,operand stack上有返回值或异常对象,不要忘记处理,不要弄丢了它们。 - 第三个角度,工作原理。 - 对于`onMethodEnter()`方法,它是借助于`visitCode()`方法来实现的。使用`onMethodEnter()`方法的优势在于,它能够处理`()`的复杂情况,而直接使用`visitCode()`方法则可能导致`()`方法出现错误。 - 对于`onMethodExit()`方法,它是借助于`visitInsn(int opcode)`方法来实现的。 ## 2. 示例: 打印方法参数和返回值 ### 2.1 预期目标 假如有一个`HelloWorld`类,代码如下: ```java public class HelloWorld { private String name; private int age; public HelloWorld(String name, int age) { this.name = name; this.age = age; } public void test(long idCard, Object obj) { int hashCode = 0; hashCode += name.hashCode(); hashCode += age; hashCode += (int) (idCard % Integer.MAX_VALUE); hashCode += obj.hashCode(); hashCode = Math.abs(hashCode); System.out.println("Hash Code is " + hashCode); if (hashCode % 2 == 1) { throw new RuntimeException("illegal"); } } } ``` 我们想实现的预期目标:打印出构造方法(`()`)和`test()`的参数和返回值。 ### 2.2 编码实现 ```java import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; public class ParameterUtils { private static final DateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static void printValueOnStack(Object value) { if (value == null) { System.out.println(" " + value); } else if (value instanceof String) { System.out.println(" " + value); } else if (value instanceof Date) { System.out.println(" " + fm.format(value)); } else if (value instanceof char[]) { System.out.println(" " + Arrays.toString((char[])value)); } else { System.out.println(" " + value.getClass() + ": " + value.toString()); } } public static void printText(String str) { System.out.println(str); } } ``` ```java import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.commons.AdviceAdapter; import static org.objectweb.asm.Opcodes.*; public class ClassPrintParameterVisitor extends ClassVisitor { public ClassPrintParameterVisitor(int api, ClassVisitor classVisitor) { super(api, classVisitor); } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); if (mv != null) { boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0; boolean isNativeMethod = (access & ACC_NATIVE) != 0; if (!isAbstractMethod && !isNativeMethod) { mv = new MethodPrintParameterAdapter(api, mv, access, name, descriptor); } } return mv; } public static class MethodPrintParameterAdapter extends AdviceAdapter { public MethodPrintParameterAdapter(int api, MethodVisitor mv, int access, String name, String descriptor) { super(api, mv, access, name, descriptor); } @Override protected void onMethodEnter() { printMessage("Method Enter: " + getName() + methodDesc); Type[] argumentTypes = getArgumentTypes(); for (int i = 0; i < argumentTypes.length; i++) { Type t = argumentTypes[i]; loadArg(i); box(t); printValueOnStack("(Ljava/lang/Object;)V"); } } @Override protected void onMethodExit(int opcode) { printMessage("Method Exit: " + getName() + methodDesc); if (opcode == ATHROW) { super.visitLdcInsn("abnormal return"); } else if (opcode == RETURN) { super.visitLdcInsn("return void"); } else if (opcode == ARETURN) { dup(); } else { if (opcode == LRETURN || opcode == DRETURN) { dup2(); } else { dup(); } box(getReturnType()); } printValueOnStack("(Ljava/lang/Object;)V"); } private void printMessage(String str) { super.visitLdcInsn(str); super.visitMethodInsn(INVOKESTATIC, "sample/ParameterUtils", "printText", "(Ljava/lang/String;)V", false); } private void printValueOnStack(String descriptor) { super.visitMethodInsn(INVOKESTATIC, "sample/ParameterUtils", "printValueOnStack", descriptor, false); } } } ``` ### 2.3 进行转换 ```java import lsieun.utils.FileUtils; import org.objectweb.asm.Cla***eader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; public class HelloWorldTransformCore { public static void main(String[] args) { String relative_path = "sample/HelloWorld.class"; String filepath = FileUtils.getFilePath(relative_path); byte[] bytes1 = FileUtils.readBytes(filepath); //(1)构建Cla***eader Cla***eader cr = new Cla***eader(bytes1); //(2)构建ClassWriter ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); //(3)串连ClassVisitor int api = Opcodes.ASM9; ClassVisitor cv = new ClassPrintParameterVisitor(api, cw); //(4)结合Cla***eader和ClassVisitor int parsingOptions = Cla***eader.SKIP_DEBUG | Cla***eader.SKIP_FRAMES; cr.accept(cv, parsingOptions); //(5)生成byte[] byte[] bytes2 = cw.toByteArray(); FileUtils.writeBytes(filepath, bytes2); } } ``` ### 2.4 验证结果 ```java import java.util.Date; public class HelloWorldRun { public static void main(String[] args) { HelloWorld instance = new HelloWorld("tomcat", 10); instance.test(441622197605122816L, new Date()); } } ``` ## 3. 总结 本文对`AdviceAdapter`类进行介绍,内容总结如下: - 第一点,在`AdviceAdapter`类当中,有两个关键的方法,即`onMethodEnter()`和`onMethodExit()`方法。我们可以从三个角度来把握这两个方法: - 从使用场景的角度来说,`AdviceAdapter`类能够很容易的在“方法进入”时和“方法退出”时添加一些代码。 - 从注意事项的角度来说,Subclasses can use or change all the local variables, but should not change state of the stack. - 从工作原理的角度来说,`onMethodEnter()`方法是借助于`visitCode()`方法来实现的;`onMethodExit()`方法是借助于`visitInsn(int opcode)`方法来实现的。 - 第二点,特殊的情况的处理。有些时候,使用`AdviceAdapter`类的`onMethodEnter()`和`onMethodExit()`方法是不能正常工作的。比如说,一些代码经过混淆(obfuscate)之后,ByteCode的内容就会变得复杂,就会出现处理不了的情况。这个时候,我们还是应该回归到`visitCode()`和`visitInsn(opcode)`方法来解决问题。

标签:AdviceAdapter,String,int,037,import,Java,方法,public
来源: https://blog.51cto.com/lsieun/2974297