<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Terry.Li-彬

    虛其心,可解天下之問;專其心,可治天下之學(xué);靜其心,可悟天下之理;恒其心,可成天下之業(yè)。

      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      143 隨筆 :: 344 文章 :: 130 評論 :: 0 Trackbacks

    一、Java ClassLoader

    1,什么是ClassLoader
    與 C 或 C++ 編寫的程序不同,Java 程序并不是一個可執(zhí)行文件,而是由許多獨(dú)立的類文件組成,每一個文件對應(yīng)于一個 Java 類。
    此外,這些類文件并非立即全部都裝入內(nèi)存,而是根據(jù)程序需要裝入內(nèi)存。ClassLoader 是 JVM 中將類裝入內(nèi)存的那部分。
    而且,Java ClassLoader 就是用 Java 語言編寫的。這意味著創(chuàng)建您自己的 ClassLoader 非常容易,不必了解 JVM 的微小細(xì)節(jié)。

    2,一些重要的方法
    A)loadClass
    ClassLoader.loadClass() 是ClassLoader的入口點(diǎn)。該方法的定義為:Class loadClass( String name, boolean resolve );
    name:JVM 需要的類的名稱,如 Foo 或 java.lang.Object。
    resolve:參數(shù)告訴方法是否需要解析類。

    B)defineClass
    defineClass方法是ClassLoader的主要訣竅。該方法接受由原始字節(jié)組成的數(shù)組并把它轉(zhuǎn)換成Class對象。

    C)findSystemClass
    findSystemClass方法從本地文件系統(tǒng)中尋找類文件,如果存在,就使用defineClass將原始字節(jié)轉(zhuǎn)換成Class對象,以將該文件轉(zhuǎn)換成類。

    D)resolveClass
    可以不完全地(不帶解析)裝入類,也可以完全地(帶解析)裝入類。當(dāng)編寫我們自己的loadClass時可以調(diào)用resolveClass,這取決于loadClass的resolve參數(shù)的值。

    E)findLoadedClass
    findLoadedClass充當(dāng)一個緩存:當(dāng)請求loadClass裝入類時,它調(diào)用該方法來查看ClassLoader是否已裝入這個類,這樣可以避免重新裝入已存在類所造成的麻煩。

    3,Java2中ClassLoader的變動
    1)loadClass的缺省實現(xiàn)
    在Java2中l(wèi)oadClass的實現(xiàn)嵌入了大多數(shù)查找類的一般方法,并使您通過覆蓋findClass方法來定制它,在適當(dāng)?shù)臅r候findClass會調(diào)用loadClass。
    這種方式的好處是可能不一定要覆蓋loadClass,只要覆蓋findClass就行了,這減少了工作量。

    2)新方法:findClass
    loadClass的缺省實現(xiàn)調(diào)用這個新方法。

    3)新方法:getSystemClassLoader
    如果覆蓋findClass或loadClass,getSystemClassLoader讓我們以實際ClassLoader對象來訪問系統(tǒng)ClassLoader,而不是固定的從findSystemClass 調(diào)用它。

    4)新方法:getParent
    為了將類請求委托給父ClassLoader,這個新方法允許ClassLoader獲取它的父ClassLoader。

    4,定制ClassLoader
    其實我們或多或少都使用過定制的ClassLoader,因為Applet查看器中就包含一個定制的ClassLoader。
    它不在本地文件系統(tǒng)中尋找類,而是訪問遠(yuǎn)程服務(wù)器上的 Web 站點(diǎn),經(jīng)過 HTTP 裝入原始的字節(jié)碼文件,并把它們轉(zhuǎn)換成JVM 內(nèi)的類。
    Applet查看器中的ClassLoader還可以做其它事情:它們支持安全性以及使不同的Applet在不同的頁面上運(yùn)行而互不干擾。
    我們將寫一個自己的ClassLoader實現(xiàn)示例,它將實現(xiàn)如下步驟,這也是ClassLoader的工作原理:
    # 調(diào)用 findLoadedClass 來查看是否存在已裝入的類。
    # 如果沒有,那么采用那種特殊的神奇方式來獲取原始字節(jié)。
    # 如果已有原始字節(jié),調(diào)用defineClass將它們轉(zhuǎn)換成Class對象。
    # 如果沒有原始字節(jié),然后調(diào)用findSystemClass查看是否從本地文件系統(tǒng)獲取類。
    # 如果resolve參數(shù)是true,那么調(diào)用resolveClass解析Class對象。
    # 如果還沒有類,返回ClassNotFoundException。
    # 否則,將類返回給調(diào)用程序。
    話不多說,看看代碼先:
    FileClassLoader.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:
    代碼
    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. }   

    編譯并運(yùn)行MyApp類,結(jié)果為:
    代碼
    1. MyApp   
    2. FileClassLoader@757aef  
    3. MyApp@9cab16  

    二、Bytecode

    1,什么是Bytecode
    C/C++編譯器把源代碼編譯成匯編代碼,Java編譯器把Java源代碼編譯成字節(jié)碼bytecode。
    Java跨平臺其實就是基于相同的bytecode規(guī)范做不同平臺的虛擬機(jī),我們的Java程序編譯成bytecode后就可以在不同平臺跑了。
    .net框架有IL(intermediate language),匯編是C/C++程序的中間表達(dá)方式,而bytecode可以說是Java平臺的中間語言。
    了解Java字節(jié)碼知識對debugging、performance tuning以及做一些高級語言擴(kuò)展或框架很有幫助。

    2,使用javap生成Bytecode
    JDK自帶的javap.exe文件可以反匯編Bytecode,讓我們看個例子:
    Test.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:
    代碼
    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:
    代碼
    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的機(jī)器,每個thread擁有一個存儲著一些frames的JVM stack,每次調(diào)用一個方法時生成一個frame。
    一個frame包括一個local variables數(shù)組(本地變量表),一個Operand LIFO stack和運(yùn)行時常量池的一個引用。

     

    我們來簡單分析一下生成的字節(jié)碼指令:
    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表示取靜態(tài)域
    invokevirtual表示調(diào)用一些實例方法
    這些指令又稱為opcode,Java一直以來只有約202個Opcode,具體請參考Java Bytecode規(guī)范。

    我們看到Test.class文件不全是二進(jìn)制的指令,有些是我們可以識別的字符,這是因為有些包名、類名和常量字符串沒有編譯成二進(jìn)制Bytecode指令。

    3,體驗字節(jié)碼增強(qiáng)的魔力
    我們J2EE常用的Hibernate、Spring都用到了動態(tài)字節(jié)碼修改來改變類的行為。
    讓我們通過看看ASM的org.objectweb.asm.MethodWriter類的部分方法來理解ASM是如何修改字節(jié)碼的:

    代碼
    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,然后根據(jù)opcode來判斷是SIPUSH指令還是BIPUSH or NEWARRAY指令,并相應(yīng)的調(diào)用字節(jié)碼修改相關(guān)的方法。

    三、ASM
    我們知道Java是靜態(tài)語言,而python、ruby是動態(tài)語言,Java程序一旦寫好很難在運(yùn)行時更改類的行為,而python、ruby可以。
    不過基于bytecode層面上我們可以做一些手腳,來使Java程序多一些靈活性和Magic,ASM就是這樣一個應(yīng)用廣泛的開源庫。

    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!"語句:

    代碼
    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兩種方式來動態(tài)生成Example類并調(diào)用打印語句。

    四、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方法來實現(xiàn)lazy loading機(jī)制,Spring則是利用cglib來實現(xiàn)動態(tài)代理。
    而JDK的動態(tài)代理機(jī)制要求有接口才行,這樣就強(qiáng)制我們的pojo實現(xiàn)某個接口。

    這里還是提供一個cglib的入門級的示例:
    MyClass.java:

    代碼
    1. public class MyClass {   
    2.   
    3.   public void print() {   
    4.     System.out.println("I'm in MyClass.print!");   
    5.   }   
    6.   
    7. }   

    Main.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. }   

    打印結(jié)果為:
    代碼
    1. public void MyClass.print() intercepted!   
    2. I'm in MyClass.print!   

    這個示例就基本上實現(xiàn)了日志AOP的功能,很簡單吧。

     

    參考資料
    CLR和JRE的運(yùn)行機(jī)制的初步總結(jié)
    Java虛擬機(jī)
    了解Java ClassLoader
    Java Virtual Machine Specification
    Java bytecode
    解讀字節(jié)碼文件
    Java Bytecode Specification and Verification
    ASM User Guide
    Hello, ASM
    cglig指南
    Java下的框架編程--cglib的應(yīng)用
    AOP = Proxy Pattern + Method Reflection + Aspect DSL + 自動代碼生成
    深入淺出Spring AOP

    posted on 2007-09-23 10:28 禮物 閱讀(2272) 評論(1)  編輯  收藏

    評論

    # re: 深入了解Java ClassLoader、Bytecode 、ASM、cglib 2008-01-24 16:15 tian
    寫的真好,贊一個:)  回復(fù)  更多評論
      


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

    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 国产情侣激情在线视频免费看| 一区二区免费视频| 亚洲美女视频网址| 免费无码又爽又刺激一高潮| 久久久久亚洲AV成人网人人网站| 污污视频免费观看网站| 亚洲AV中文无码乱人伦在线视色| 久久亚洲精品无码av| 四虎国产精品免费久久影院| 亚洲电影一区二区三区| 久久永久免费人妻精品| 91情国产l精品国产亚洲区| 亚洲视频免费在线播放| 亚洲制服在线观看| 最近中文字幕无吗免费高清| 亚洲日韩中文字幕无码一区| 永久免费看bbb| 日本精品久久久久久久久免费| 国产亚洲精品高清在线| 精品在线免费观看| 国产精品亚洲αv天堂无码| kk4kk免费视频毛片| 亚洲AV综合色一区二区三区| 国产av无码专区亚洲av毛片搜| 亚洲?V乱码久久精品蜜桃| 中文字幕免费在线看电影大全| 中文字幕亚洲精品| 日本特黄a级高清免费大片| 一级毛片免费不卡直观看| 亚洲AV无码一区二区三区DV| 四虎免费影院ww4164h| 亚洲av午夜电影在线观看| 337p日本欧洲亚洲大胆裸体艺术| 久久精品国产免费一区| 亚洲国产日韩在线| 亚洲成?Ⅴ人在线观看无码| 国产免费AV片在线观看| 亚洲中文字幕一二三四区苍井空| 一区国严二区亚洲三区| 免费国产黄网站在线观看可以下载| 亚洲人成色77777|