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 閱讀(318) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 大庆市| 海城市| 南丰县| 广河县| 乃东县| 图木舒克市| 鄂托克前旗| 电白县| 剑川县| 陈巴尔虎旗| 旬邑县| 乐都县| 河北省| 南和县| 广河县| 巩留县| 吉木乃县| 祁东县| 灌南县| 漳州市| 大丰市| 石家庄市| 临沂市| 正阳县| 敦化市| 天祝| 彰化市| 北碚区| 搜索| 井研县| 岢岚县| 会理县| 富锦市| 武城县| 房山区| 新干县| 宜州市| 兴宁市| 小金县| 万盛区| 天等县|