dream.in.java

能以不变应万变是聪明人做事的准则。万事从小事做起,积累小成功,问鼎大成功,是成功者的秘诀。

深入了解Java ClassLoader、Bytecode 、ASM、cglib

Java代码 复制代码
  1. import java.io.ByteArrayOutputStream;   
  2. import java.io.File;   
  3. import java.io.FileInputStream;   
  4. import java.io.IOException;   
  5.   
  6. public class FileClassLoader extends ClassLoader {   
  7.   public Class findClass(String name) {   
  8.     byte[] data = loadClassData(name);   
  9.     return defineClass(name, data, 0, data.length);   
  10.   }   
  11.      
  12.   private byte[] loadClassData(String name) {   
  13.     FileInputStream fis = null;   
  14.     byte[] data = null;   
  15.     try {   
  16.       fis = new FileInputStream(new File("D:\\project\\test\\" + name + ".class"));   
  17.       ByteArrayOutputStream baos = new ByteArrayOutputStream();   
  18.       int ch = 0;   
  19.       while ((ch = fis.read()) != -1) {   
  20.         baos.write(ch);   
  21.       }   
  22.       data = baos.toByteArray();   
  23.     } catch (IOException e) {   
  24.       e.printStackTrace();   
  25.     }   
  26.     return data;   
  27.   }   
  28. }  

MyApp.java:
Java代码 复制代码
  1. public class MyApp {   
  2.   public static void main(String[] args) throws Exception {   
  3.     FileClassLoader loader = new FileClassLoader();   
  4.     Class objClass = loader.findClass("MyApp");   
  5.     Object obj = objClass.newInstance();   
  6.     System.out.println(objClass.getName());   
  7.     System.out.println(objClass.getClassLoader());   
  8.     System.out.println(obj);   
  9.   }   
  10. }  

编译并运行MyApp类,结果为:
Java代码 复制代码
  1. MyApp   
  2. FileClassLoader@757aef  
  3. MyApp@9cab16  




二、Bytecode

1,什么是Bytecode
C/C++编译器把源代码编译成汇编代码,Java编译器把Java源代码编译成字节码bytecode。
Java跨平台其实就是基于相同的bytecode规范做不同平台的虚拟机,我们的Java程序编译成bytecode后就可以在不同平台跑了。
.net框架有IL(intermediate language),汇编是C/C++程序的中间表达方式,而bytecode可以说是Java平台的中间语言。
了解Java字节码知识对debugging、performance tuning以及做一些高级语言扩展或框架很有帮助。

2,使用javap生成Bytecode
JDK自带的javap.exe文件可以反汇编Bytecode,让我们看个例子:
Test.java:
Java代码 复制代码
  1. public class Test {   
  2.   public static void main(String[] args) {   
  3.     int i = 10000;   
  4.     System.out.println("Hello Bytecode! Number = " + i);   
  5.   }   
  6. }  

编译后的Test.class:
Java代码 复制代码
  1. 漱壕   1 +   
  2.            
  3.       
  4.      
  5.      
  6.      
  7.      <init> ()V Code LineNumberTable main ([Ljava/lang/String;)V    
  8. SourceFile   Test.java       
  9.     ! " java/lang/StringBuilder Hello Bytecode! Number =   # $  # %  & ' (  ) * Test java/lang/Object java/lang/System out Ljava/io/PrintStream; append -(Ljava/lang/String;)Ljava/lang/StringBuilder; (I)Ljava/lang/StringBuilder; toString ()Ljava/lang/String; java/io/PrintStream println (Ljava/lang/String;)V !    
  10.               
  11.           *                      >     '<  Y                              

使用javap -c Test > Test.bytecode生成的Test.bytecode:
Java代码 复制代码
  1. Compiled from "Test.java"  
  2. public class Test extends java.lang.Object{   
  3. public Test();   
  4.   Code:   
  5.    0:  aload_0   
  6.    1:  invokespecial  #1//Method java/lang/Object."<init>":()V   
  7.    4:  return  
  8.   
  9. public static void main(java.lang.String[]);   
  10.   Code:   
  11.    0:  sipush  10000  
  12.    3:  istore_1   
  13.    4:  getstatic  #2//Field java/lang/System.out:Ljava/io/PrintStream;   
  14.    7:  new  #3//class java/lang/StringBuilder   
  15.    10:  dup   
  16.    11:  invokespecial  #4//Method java/lang/StringBuilder."<init>":()V   
  17.    14:  ldc  #5//String Hello Bytecode! Number =    
  18.    16:  invokevirtual  #6//Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;   
  19.    19:  iload_1   
  20.    20:  invokevirtual  #7//Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;   
  21.    23:  invokevirtual  #8//Method java/lang/StringBuilder.toString:()Ljava/lang/String;   
  22.    26:  invokevirtual  #9//Method java/io/PrintStream.println:(Ljava/lang/String;)V   
  23.    29:  return  
  24.   
  25. }  

JVM就是一个基于stack的机器,每个thread拥有一个存储着一些frames的JVM stack,每次调用一个方法时生成一个frame。
一个frame包括一个local variables数组(本地变量表),一个Operand LIFO stack和运行时常量池的一个引用。

我们来简单分析一下生成的字节码指令:
aload和iload指令的“a”前缀和“i”分别表示对象引用和int类型,其他还有“b”表示byte,“c”表示char,“d”表示double等等
我们这里的aload_0表示将把local variable table中index 0的值push到Operand stack,iload_1类似
invokespecial表示初始化对象,return表示返回
sipush表示把10000这个int值push到Operand stack
getstatic表示取静态域
invokevirtual表示调用一些实例方法
这些指令又称为opcode,Java一直以来只有约202個Opcode,具体请参考Java Bytecode规范。

我们看到Test.class文件不全是二进制的指令,有些是我们可以识别的字符,这是因为有些包名、类名和常量字符串没有编译成二进制Bytecode指令。

3,体验字节码增强的魔力
我们J2EE常用的Hibernate、Spring都用到了动态字节码修改来改变类的行为。
让我们通过看看ASM的org.objectweb.asm.MethodWriter类的部分方法来理解ASM是如何修改字节码的:
Java代码 复制代码
  1. class MethodWriter implements MethodVisitor {   
  2.   
  3.     private ByteVector code = new ByteVector();   
  4.   
  5.     public void visitIntInsn(final int opcode, final int operand) {   
  6.         // Label currentBlock = this.currentBlock;   
  7.         if (currentBlock != null) {   
  8.             if (compute == FRAMES) {   
  9.                 currentBlock.frame.execute(opcode, operand, nullnull);   
  10.             } else if (opcode != Opcodes.NEWARRAY) {   
  11.                 // updates current and max stack sizes only for NEWARRAY   
  12.                 // (stack size variation = 0 for BIPUSH or SIPUSH)   
  13.                 int size = stackSize + 1;   
  14.                 if (size > maxStackSize) {   
  15.                     maxStackSize = size;   
  16.                 }   
  17.                 stackSize = size;   
  18.             }   
  19.         }   
  20.         // adds the instruction to the bytecode of the method   
  21.         if (opcode == Opcodes.SIPUSH) {   
  22.             code.put12(opcode, operand);   
  23.         } else { // BIPUSH or NEWARRAY   
  24.             code.put11(opcode, operand);   
  25.         }   
  26.     }   
  27.   
  28.     public void visitMethodInsn(   
  29.         final int opcode,   
  30.         final String owner,   
  31.         final String name,   
  32.         final String desc)   
  33.     {   
  34.         boolean itf = opcode == Opcodes.INVOKEINTERFACE;   
  35.         Item i = cw.newMethodItem(owner, name, desc, itf);   
  36.         int argSize = i.intVal;   
  37.         // Label currentBlock = this.currentBlock;   
  38.         if (currentBlock != null) {   
  39.             if (compute == FRAMES) {   
  40.                 currentBlock.frame.execute(opcode, 0, cw, i);   
  41.             } else {   
  42.                 /*  
  43.                  * computes the stack size variation. In order not to recompute  
  44.                  * several times this variation for the same Item, we use the  
  45.                  * intVal field of this item to store this variation, once it  
  46.                  * has been computed. More precisely this intVal field stores  
  47.                  * the sizes of the arguments and of the return value  
  48.                  * corresponding to desc.  
  49.                  */  
  50.                 if (argSize == 0) {   
  51.                     // the above sizes have not been computed yet,   
  52.                     // so we compute them...   
  53.                     argSize = getArgumentsAndReturnSizes(desc);   
  54.                     // ... and we save them in order   
  55.                     // not to recompute them in the future   
  56.                     i.intVal = argSize;   
  57.                 }   
  58.                 int size;   
  59.                 if (opcode == Opcodes.INVOKESTATIC) {   
  60.                     size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1;   
  61.                 } else {   
  62.                     size = stackSize - (argSize >> 2) + (argSize & 0x03);   
  63.                 }   
  64.                 // updates current and max stack sizes   
  65.                 if (size > maxStackSize) {   
  66.                     maxStackSize = size;   
  67.                 }   
  68.                 stackSize = size;   
  69.             }   
  70.         }   
  71.         // adds the instruction to the bytecode of the method   
  72.         if (itf) {   
  73.             if (argSize == 0) {   
  74.                 argSize = getArgumentsAndReturnSizes(desc);   
  75.                 i.intVal = argSize;   
  76.             }   
  77.             code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 20);   
  78.         } else {   
  79.             code.put12(opcode, i.index);   
  80.         }   
  81.     }   
  82. }  

通过注释我们可以大概理解visitIntInsn和visitMethodInsn方法的意思。
比如visitIntInsn先计算stack的size,然后根据opcode来判断是SIPUSH指令还是BIPUSH or NEWARRAY指令,并相应的调用字节码修改相关的方法。


三、ASM
我们知道Java是静态语言,而python、ruby是动态语言,Java程序一旦写好很难在运行时更改类的行为,而python、ruby可以。
不过基于bytecode层面上我们可以做一些手脚,来使Java程序多一些灵活性和Magic,ASM就是这样一个应用广泛的开源库。

ASM is a Java bytecode manipulation framework. It can be used to dynamically generate stub classes or other proxy classes,
directly in binary form, or to dynamically modify classes at load time, i.e., just before they are loaded into the Java
Virtual Machine.

ASM完成了BCELSERP同样的功能,但ASM
只有30多k,而后两者分别是350k和150k。apache真是越来越过气了。

让我们来看一个ASM的简单例子Helloworld.java,它生成一个Example类和一个main方法,main方法打印"Hello world!"语句:
Java代码 复制代码
  1. import java.io.FileOutputStream;   
  2. import java.io.PrintStream;   
  3.   
  4. import org.objectweb.asm.ClassWriter;   
  5. import org.objectweb.asm.MethodVisitor;   
  6. import org.objectweb.asm.Opcodes;   
  7. import org.objectweb.asm.Type;   
  8. import org.objectweb.asm.commons.GeneratorAdapter;   
  9. import org.objectweb.asm.commons.Method;   
  10.   
  11. public class Helloworld extends ClassLoader implements Opcodes {   
  12.   
  13.   public static void main(final String args[]) throws Exception {   
  14.   
  15.     // creates a ClassWriter for the Example public class,   
  16.     // which inherits from Object   
  17.   
  18.     ClassWriter cw = new ClassWriter(0);   
  19.     cw.visit(V1_1, ACC_PUBLIC, "Example"null"java/lang/Object"null);   
  20.     MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>""()V"null,   
  21.         null);   
  22.     mw.visitVarInsn(ALOAD, 0);   
  23.     mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object""<init>""()V");   
  24.     mw.visitInsn(RETURN);   
  25.     mw.visitMaxs(11);   
  26.     mw.visitEnd();   
  27.     mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main",   
  28.         "([Ljava/lang/String;)V"nullnull);   
  29.     mw.visitFieldInsn(GETSTATIC, "java/lang/System""out",   
  30.         "Ljava/io/PrintStream;");   
  31.     mw.visitLdcInsn("Hello world!");   
  32.     mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream""println",   
  33.         "(Ljava/lang/String;)V");   
  34.     mw.visitInsn(RETURN);   
  35.     mw.visitMaxs(22);   
  36.     mw.visitEnd();   
  37.     byte[] code = cw.toByteArray();   
  38.     FileOutputStream fos = new FileOutputStream("Example.class");   
  39.     fos.write(code);   
  40.     fos.close();   
  41.     Helloworld loader = new Helloworld();   
  42.     Class exampleClass = loader   
  43.         .defineClass("Example", code, 0, code.length);   
  44.     exampleClass.getMethods()[0].invoke(nullnew Object[] { null });   
  45.   
  46.     // ------------------------------------------------------------------------   
  47.     // Same example with a GeneratorAdapter (more convenient but slower)   
  48.     // ------------------------------------------------------------------------   
  49.   
  50.     cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);   
  51.     cw.visit(V1_1, ACC_PUBLIC, "Example"null"java/lang/Object"null);   
  52.     Method m = Method.getMethod("void <init> ()");   
  53.     GeneratorAdapter mg = new GeneratorAdapter(ACC_PUBLIC, m, nullnull,   
  54.         cw);   
  55.     mg.loadThis();   
  56.     mg.invokeConstructor(Type.getType(Object.class), m);   
  57.     mg.returnValue();   
  58.     mg.endMethod();   
  59.     m = Method.getMethod("void main (String[])");   
  60.     mg = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC, m, nullnull, cw);   
  61.     mg.getStatic(Type.getType(System.class), "out", Type   
  62.         .getType(PrintStream.class));   
  63.     mg.push("Hello world!");   
  64.     mg.invokeVirtual(Type.getType(PrintStream.class), Method   
  65.         .getMethod("void println (String)"));   
  66.     mg.returnValue();   
  67.     mg.endMethod();   
  68.     cw.visitEnd();   
  69.     code = cw.toByteArray();   
  70.     loader = new Helloworld();   
  71.     exampleClass = loader.defineClass("Example", code, 0, code.length);   
  72.     exampleClass.getMethods()[0].invoke(nullnew Object[] { null });   
  73.   }   
  74. }  

我们看到上面的例子分别使用ASM的MethodVisitor和GeneratorAdapter两种方式来动态生成Example类并调用打印语句。

四、cglib
cglib is a powerful, high performance and quality Code Generation Library, It is used to extend JAVA classes and implements interfaces at runtime.
cglib是Code Generation Library的缩写。
cglib依赖于ASM库。
Hibernate主要是利用cglib生成pojo的子类并override get方法来实现lazy loading机制,Spring则是利用cglib来实现动态代理。
而JDK的动态代理机制要求有接口才行,这样就强制我们的pojo实现某个接口。

这里还是提供一个cglib的入门级的示例:
MyClass.java:
Java代码 复制代码
  1. public class MyClass {   
  2.   
  3.   public void print() {   
  4.     System.out.println("I'm in MyClass.print!");   
  5.   }   
  6.   
  7. }  

Main.java:
Java代码 复制代码
  1. import java.lang.reflect.Method;   
  2. import net.sf.cglib.proxy.Enhancer;   
  3. import net.sf.cglib.proxy.MethodInterceptor;   
  4. import net.sf.cglib.proxy.MethodProxy;   
  5.   
  6. public class Main {   
  7.   
  8.   public static void main(String[] args) {   
  9.   
  10.     Enhancer enhancer = new Enhancer();   
  11.     enhancer.setSuperclass(MyClass.class);   
  12.     enhancer.setCallback(new MethodInterceptorImpl());   
  13.     MyClass my = (MyClass) enhancer.create();   
  14.     my.print();   
  15.   }   
  16.   
  17.   private static class MethodInterceptorImpl implements MethodInterceptor {   
  18.     public Object intercept(Object obj, Method method, Object[] args,   
  19.         MethodProxy proxy) throws Throwable {   
  20.       // log something   
  21.       System.out.println(method + " intercepted!");   
  22.   
  23.       proxy.invokeSuper(obj, args);   
  24.       return null;   
  25.     }   
  26.   }   
  27. }  

打印结果为:
Java代码 复制代码
  1. public void MyClass.print() intercepted!   
  2. I'm in MyClass.print!  

这个示例就基本上实现了日志AOP的功能,很简单吧。

参考资料
CLR和JRE的运行机制的初步总结
Java虚拟机
了解Java ClassLoader
Java Virtual Machine Specification
Java bytecode
解读字节码文件
Java Bytecode Specification and Verification
ASM User Guide
Hello, ASM
cglig指南
Java下的框架编程--cglib的应用
AOP = Proxy Pattern + Method Reflection + Aspect DSL + 自动代码生成
深入浅出Spring AOP

Traceback:http://www.javaeye.com/topic/98178

posted on 2009-04-14 09:22 YXY 阅读(310) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航: