今天要说的是java字节码的增强,按照惯例,先上代码以后再补说明。
首先是自己定义的一个字节码增强类,采用的是ASM来操纵的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 /** 2 * 3 * @author rui.chen 4 * 5 */ 6 public class ClassAdvice { 7 /** 8 * 前增强,在每个方法执行前打印出"Enter Method -> + 方法名" 9 * @param className 要增强的类名 10 */ 11 public void beforeAdvice(String className){ 12 try { 13 ClassReader reader = new ClassReader(new FileInputStream(className+".class")); 14 ClassNode cn = new ClassNode(); 15 reader.accept(cn, 0); 16 for(Object obj:cn.methods){ 17 MethodNode mn = (MethodNode) obj; 18 if("<init>".equals(mn.name) || "clinit".equals(mn.name)){ 19 continue; 20 } 21 InsnList insns = mn.instructions; 22 InsnList il = new InsnList(); 23 il.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); 24 il.add(new LdcInsnNode("Enter Method ->"+mn.name)); 25 il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V")); 26 insns.insert(il); 27 mn.maxStack += 3; 28 } 29 ClassWriter writer = new ClassWriter(0); 30 cn.accept(writer); 31 byte[] bs = writer.toByteArray(); 32 FileOutputStream out = new FileOutputStream(className+".class"); 33 out.write(bs); 34 out.close(); 35 } catch (FileNotFoundException e) { 36 e.printStackTrace(); 37 } catch (IOException e) { 38 e.printStackTrace(); 39 } 40 } 41 42 }
然后我们先看一下上一篇中自己编译的类SaySomething的class文件结构,主要关心saySomething方法的字节码。class文件结构的查看可以采用ASM的ASMifierClassVisitor工具,调用其main方法即可打印。
/** * SaySomething.java * **/ public class SaySomething { public void saySomething(String sth){ System.out.println(sth); } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 import java.util.*; 2 import org.objectweb.asm.*; 3 import org.objectweb.asm.attrs.*; 4 import org.objectweb.asm.util.ASMifierClassVisitor; 5 /** 6 * 使用ASMifierClassVisitor打印结果,命令为ASMifierClassVisitor.main(new String[]{className+".class"}); 7 * @author rui.chen 8 * 9 */ 10 public class SaySomethingDump implements Opcodes { 11 12 public static byte[] dump () throws Exception { 13 14 ClassWriter cw = new ClassWriter(0); 15 FieldVisitor fv; 16 MethodVisitor mv; 17 AnnotationVisitor av0; 18 19 cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "SaySomething", null, "java/lang/Object", null); 20 21 { 22 mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 23 mv.visitCode(); 24 mv.visitVarInsn(ALOAD, 0); 25 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); 26 mv.visitInsn(RETURN); 27 mv.visitMaxs(1, 1); 28 mv.visitEnd(); 29 } 30 { 31 mv = cw.visitMethod(ACC_PUBLIC, "saySomething", "(Ljava/lang/String;)V", null, null); 32 mv.visitCode(); 33 mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); 34 mv.visitVarInsn(ALOAD, 1); 35 mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"); 36 mv.visitInsn(RETURN); 37 mv.visitMaxs(2, 2); 38 mv.visitEnd(); 39 } 40 cw.visitEnd(); 41 42 return cw.toByteArray(); 43 } 44 }
然后我们通过一个测试类调用字节码的前增强,看一下字节码发生了哪些变化。
1 ClassAdvice advice = new ClassAdvice(); 2 advice.beforeAdvice("SaySomething");
1 //注意观察多出来的三行 2 { 3 mv = cw.visitMethod(ACC_PUBLIC, "saySomething", "(Ljava/lang/String;)V", null, null); 4 mv.visitCode(); 5 //访问域成员System.out 6 mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); 7 //将字符串推送至栈顶 8 mv.visitLdcInsn("Enter Method ->saySomething"); 9 //调用out成员的println方法 10 mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"); 11 mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); 12 mv.visitVarInsn(ALOAD, 1); 13 mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"); 14 mv.visitInsn(RETURN); 15 mv.visitMaxs(5, 2); 16 mv.visitEnd(); 17 }
最后通过反射调用saySomething方法,得到结果如下:
1 public class AdviceTest { 2 public static void main(String[] args) throws ClassNotFoundException, 3 SecurityException, NoSuchMethodException, IllegalArgumentException, 4 IllegalAccessException, InvocationTargetException, InstantiationException{ 5 MyClassLoader loader = new MyClassLoader("."); 6 Class<?> clazz = loader.loadClass("SaySomething"); 7 Method method = clazz.getMethod("saySomething", new Class[]{String.class}); 8 method.invoke(clazz.newInstance(), "Hello, Rui.Chen!"); 9 } 10 }
1 Enter Method ->saySomething 2 Hello, Rui.Chen!
控制台打印出了"Enter Method ->saySomething",增强成功!