作者:陈科
联系方式:chenke@dumpcache.com
转载请说明出处:http://www.dumpcache.com/wiki/doku.php?id=about_asm
最近把asm的大多数功能点都使用了一下,我总结一下使用过程中的一些场景和解决方案
一般来讲字节码增强在aop的场景用的比较多,所以你可以能会在原先的class中插入一些java字节码,自己手写失误率是很高的,所以建议先把你要插入的功能写到一个测试类中,并且使用javap命令进行反编译参考样例再插入。
比如:
public class Example { public static String get() { System.out.println("Hello world"); return "aaa"; } public static void main(String args[]) { System.out.println(get()); } }
上面的Example反编译之后:
javap -c Example
结果成了:
Compiled from "Example.java" public class Example { public Example(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static java.lang.String get(); Code: 0: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #22 // String Hello world 5: invokevirtual #24 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: ldc #30 // String aaa 10: areturn public static void main(java.lang.String[]); Code: 0: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 3: invokestatic #34 // Method get:()Ljava/lang/String; 6: invokevirtual #24 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 9: return }
以上面的example为例,get方法返回为String,所以,在return前栈顶需要有数据,所以上面执行了:
8: ldc #30 // String aaa
当我增强代码的时候,建议使用:AdviceAdapter
但是当我们在调用:onMethodExit方法的时候,result结果已经压入栈中了,所以我在onMethodExit中再进行其他处理必然会出错。
我这里通过一个简单的栈操作来进行结果的打印:
dup(); visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); dupX1(); pop(); visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
dup/pop等指令对栈的影响入下图:
假如get方法会抛出RuntimeException,我们想抓住异常做一些处理,那么就需要先保存函数返回值,否则插入的代码会在ldc之后,将会出现问题,为了做到这一点,我构建了一个Holder来保存返回值:
public class Holder { private static ThreadLocal<String> holder = new ThreadLocal<String>(); public static void save(String str) { holder.set(str); } public static String load() { String str = holder.get(); holder.remove(); return str; } }
最后,AdviceAdapter的代码如下:
public static class ModifierMethodWriter extends AdviceAdapter { // methodName to make sure adding try catch block for the specific // method. private String methodName; // below label variables are for adding try/catch blocks in instrumented // code. private Label lTryBlockStart; private Label lTryBlockEnd; private Label lCatchBlockStart; private Label lCatchBlockEnd; /** * constructor for accepting methodVisitor object and methodName * * @param api: the ASM API version implemented by this visitor * @param mv: MethodVisitor obj * @param methodName : methodName to make sure adding try catch block * for the specific method. */ public ModifierMethodWriter(MethodVisitor mv, String className, int access, String name, String desc) { super(Opcodes.ASM4, mv, access, name, desc); this.methodName = name; } // We want to add try/catch block for the entire code in the method // so adding the try/catch when the method is started visiting the code. @Override public void onMethodEnter() { // // adding try/catch block only if the method is hello() if (methodName.equals("get")) { lTryBlockStart = new Label(); lTryBlockEnd = new Label(); lCatchBlockStart = new Label(); lCatchBlockEnd = new Label(); // set up try-catch block for RuntimeException visitTryCatchBlock(lTryBlockStart, lTryBlockEnd, lCatchBlockStart, "java/lang/Exception"); // started the try block visitLabel(lTryBlockStart); } } @Override public void onMethodExit(int opcode) { // closing the try block and opening the catch block if the method // is hello() if (methodName.equals("get")) { visitMethodInsn(INVOKESTATIC, "Holder", "save", "(Ljava/lang/String;)V", false); // closing the try block visitLabel(lTryBlockEnd); // when here, no exception was thrown, so skip exception handler visitJumpInsn(Opcodes.GOTO, lCatchBlockEnd); // exception handler starts here, with RuntimeException stored // on stack visitLabel(lCatchBlockStart); //mv.visitInsn(ATHROW); // store the RuntimeException in local variable visitVarInsn(Opcodes.ASTORE, 2); // here we could for example do e.printStackTrace() visitVarInsn(Opcodes.ALOAD, 2); // load it visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V", false); // exception handler ends here: visitLabel(lCatchBlockEnd); //visitVarInsn(Opcodes.ALOAD,1); visitMethodInsn(INVOKESTATIC, "Holder", "load", "()Ljava/lang/String;", false); } } }
为了尽量减少插入代码的影响,建议构建static方法来调用:
public class DefaultSampleCollector implements SampleCollector { private Map<String, CollectPolicy> policyMap = new ConcurrentHashMap<String, CollectPolicy>(); private BlockingQueue<Sample> queue; public DefaultSampleCollector(BlockingQueue<Sample> queue) { } @Override public void collect(String method, long startTime, long endTime, Object returnVal, Object... params) { System.out.println(params[0]); System.out.println(startTime); //System.out.println(method); }
最后我们来看插入的逻辑:
public class JedisMethodVisitor extends AdviceAdapter { private static final String VOID_J = "()J"; private static final String CURRENT_TIME_MILLIS = "currentTimeMillis"; private static final String JAVA_LANG_SYSTEM = "java/lang/System"; private int time; private String name; public JedisMethodVisitor(MethodVisitor mv, String className, int access, String name, String desc) { super(Opcodes.ASM4, mv, access, name, desc); this.name = name; } @Override protected void onMethodEnter() { time = this.newLocal(Type.LONG_TYPE); visitMethodInsn(Opcodes.INVOKESTATIC, JAVA_LANG_SYSTEM, CURRENT_TIME_MILLIS, VOID_J, false); this.visitVarInsn(Opcodes.LSTORE, time); // visitMethodInsn(INVOKESTATIC, // "com/kuaidadi/framework/fusion/monitor/SampleCollectorHolder", "collect", "()V", // false); } @Override protected void onMethodExit(int opcode) { // dup(); // visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); // dupX1(); // pop(); // visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", // false); if (opcode == ARETURN) { dup(); push(name); visitVarInsn(LLOAD, time); time = this.newLocal(Type.LONG_TYPE); visitMethodInsn(Opcodes.INVOKESTATIC, JAVA_LANG_SYSTEM, CURRENT_TIME_MILLIS, VOID_J, false); this.visitVarInsn(Opcodes.LSTORE, time); visitVarInsn(LLOAD, time); loadArgArray(); visitMethodInsn(INVOKESTATIC, "com/kuaidadi/framework/fusion/monitor/SampleCollectorHolder", "collect", "(Ljava/lang/Object;Ljava/lang/String;JJ[Ljava/lang/Object;)V", false); } } // @Override // public void visitMaxs(int maxStack, int maxLocals) { // super.visitMaxs(maxStack + 4, maxLocals); // } }