轉載自http://www.iteye.com/topic/98178
一、Java ClassLoader ?
1,什么是ClassLoader ?
與 C 或 C++ 編寫的程序不同,Java 程序并不是一個可執行文件,而是由許多獨立的類文件組成,每一個文件對應于一個 Java 類。?
此外,這些類文件并非立即全部都裝入內存,而是根據程序需要裝入內存。ClassLoader 是 JVM 中將類裝入內存的那部分。?
而且,Java ClassLoader 就是用 Java 語言編寫的。這意味著創建您自己的 ClassLoader 非常容易,不必了解 JVM 的微小細節。?
2,一些重要的方法 ?
A)loadClass?
ClassLoader.loadClass() 是ClassLoader的入口點。該方法的定義為:Class loadClass( String name, boolean resolve );?
name:JVM 需要的類的名稱,如 Foo 或 java.lang.Object。?
resolve:參數告訴方法是否需要解析類。?
B)defineClass?
defineClass方法是ClassLoader的主要訣竅。該方法接受由原始字節組成的數組并把它轉換成Class對象。?
C)findSystemClass?
findSystemClass方法從本地文件系統中尋找類文件,如果存在,就使用defineClass將原始字節轉換成Class對象,以將該文件轉換成類。?
D)resolveClass?
可以不完全地(不帶解析)裝入類,也可以完全地(帶解析)裝入類。當編寫我們自己的loadClass時可以調用resolveClass,這取決于loadClass的resolve參數的值。?
E)findLoadedClass?
findLoadedClass充當一個緩存:當請求loadClass裝入類時,它調用該方法來查看ClassLoader是否已裝入這個類,這樣可以避免重新裝入已存在類所造成的麻煩。?
3,Java2中ClassLoader的變動 ?
1)loadClass的缺省實現?
在Java2中loadClass的實現嵌入了大多數查找類的一般方法,并使您通過覆蓋findClass方法來定制它,在適當的時候findClass會調用loadClass。?
這種方式的好處是可能不一定要覆蓋loadClass,只要覆蓋findClass就行了,這減少了工作量。?
2)新方法:findClass?
loadClass的缺省實現調用這個新方法。?
3)新方法:getSystemClassLoader?
如果覆蓋findClass或loadClass,getSystemClassLoader讓我們以實際ClassLoader對象來訪問系統ClassLoader,而不是固定的從findSystemClass 調用它。?
4)新方法:getParent?
為了將類請求委托給父ClassLoader,這個新方法允許ClassLoader獲取它的父ClassLoader。?
4,定制ClassLoader ?
其實我們或多或少都使用過定制的ClassLoader,因為Applet查看器中就包含一個定制的ClassLoader。?
它不在本地文件系統中尋找類,而是訪問遠程服務器上的 Web 站點,經過 HTTP 裝入原始的字節碼文件,并把它們轉換成JVM 內的類。?
Applet查看器中的ClassLoader還可以做其它事情:它們支持安全性以及使不同的Applet在不同的頁面上運行而互不干擾。?
我們將寫一個自己的ClassLoader實現示例,它將實現如下步驟,這也是ClassLoader的工作原理:?
# 調用 findLoadedClass 來查看是否存在已裝入的類。?
# 如果沒有,那么采用那種特殊的神奇方式來獲取原始字節。?
# 如果已有原始字節,調用defineClass將它們轉換成Class對象。?
# 如果沒有原始字節,然后調用findSystemClass查看是否從本地文件系統獲取類。?
# 如果resolve參數是true,那么調用resolveClass解析Class對象。?
# 如果還沒有類,返回ClassNotFoundException。?
# 否則,將類返回給調用程序。?
話不多說,看看代碼先:?
FileClassLoader.java:?
Java代碼??
import?java.io.ByteArrayOutputStream;?? import?java.io.File;?? import?java.io.FileInputStream;?? import?java.io.IOException;?? ?? public?class?FileClassLoader?extends?ClassLoader?{?? ??public?Class?findClass(String?name)?{?? ????byte[]?data?=?loadClassData(name);?? ????return?defineClass(name,?data,?0,?data.length);?? ??}?? ???? ??private?byte[]?loadClassData(String?name)?{?? ????FileInputStream?fis?=?null;?? ????byte[]?data?=?null;?? ????try?{?? ??????fis?=?new?FileInputStream(new?File("D:\\project\\test\\"?+?name?+?".class"));?? ??????ByteArrayOutputStream?baos?=?new?ByteArrayOutputStream();?? ??????int?ch?=?0;?? ??????while?((ch?=?fis.read())?!=?-1)?{?? ????????baos.write(ch);?? ??????}?? ??????data?=?baos.toByteArray();?? ????}?catch?(IOException?e)?{?? ??????e.printStackTrace();?? ????}?? ????return?data;?? ??}?? }?? MyApp.java:?
Java代碼??
public?class?MyApp?{?? ??public?static?void?main(String[]?args)?throws?Exception?{?? ????FileClassLoader?loader?=?new?FileClassLoader();?? ????Class?objClass?=?loader.findClass("MyApp");?? ????Object?obj?=?objClass.newInstance();?? ????System.out.println(objClass.getName());?? ????System.out.println(objClass.getClassLoader());?? ????System.out.println(obj);?? ??}?? }?? 編譯并運行MyApp類,結果為:?
Java代碼??
MyApp?? FileClassLoader@757aef?? 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代碼??
public?class?Test?{?? ??public?static?void?main(String[]?args)?{?? ????int?i?=?10000;?? ????System.out.println("Hello?Bytecode!?Number?=?"?+?i);?? ??}?? }?? 編譯后的Test.class:?
Java代碼??
漱壕???1?+?? ????????? ????? ???? ???? ???? ?????<init>?()V?Code?LineNumberTable?main?([Ljava/lang/String;)V??? SourceFile???Test.java???? ???!?"?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?!??? ??????????? ??????????*??????????????????????>?????'<??Y?????????????????????????????? 使用javap -c Test > Test.bytecode生成的Test.bytecode:?
Java代碼??
Compiled?from?"Test.java"?? public?class?Test?extends?java.lang.Object{?? public?Test();?? ??Code:?? ???0:??aload_0?? ???1:??invokespecial??#1;? ???4:??return?? ?? public?static?void?main(java.lang.String[]);?? ??Code:?? ???0:??sipush??10000?? ???3:??istore_1?? ???4:??getstatic??#2;? ???7:??new??#3;? ???10:??dup?? ???11:??invokespecial??#4;? ???14:??ldc??#5;? ???16:??invokevirtual??#6;? ???19:??iload_1?? ???20:??invokevirtual??#7;? ???23:??invokevirtual??#8;? ???26:??invokevirtual??#9;? ???29:??return?? ?? }?? 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代碼??
class?MethodWriter?implements?MethodVisitor?{?? ?? ????private?ByteVector?code?=?new?ByteVector();?? ?? ????public?void?visitIntInsn(final?int?opcode,?final?int?operand)?{?? ???????? ????????if?(currentBlock?!=?null)?{?? ????????????if?(compute?==?FRAMES)?{?? ????????????????currentBlock.frame.execute(opcode,?operand,?null,?null);?? ????????????}?else?if?(opcode?!=?Opcodes.NEWARRAY)?{?? ???????????????? ???????????????? ????????????????int?size?=?stackSize?+?1;?? ????????????????if?(size?>?maxStackSize)?{?? ????????????????????maxStackSize?=?size;?? ????????????????}?? ????????????????stackSize?=?size;?? ????????????}?? ????????}?? ???????? ????????if?(opcode?==?Opcodes.SIPUSH)?{?? ????????????code.put12(opcode,?operand);?? ????????}?else?{? ????????????code.put11(opcode,?operand);?? ????????}?? ????}?? ?? ????public?void?visitMethodInsn(?? ????????final?int?opcode,?? ????????final?String?owner,?? ????????final?String?name,?? ????????final?String?desc)?? ????{?? ????????boolean?itf?=?opcode?==?Opcodes.INVOKEINTERFACE;?? ????????Item?i?=?cw.newMethodItem(owner,?name,?desc,?itf);?? ????????int?argSize?=?i.intVal;?? ???????? ????????if?(currentBlock?!=?null)?{?? ????????????if?(compute?==?FRAMES)?{?? ????????????????currentBlock.frame.execute(opcode,?0,?cw,?i);?? ????????????}?else?{?? ???????????????? ????????????????if?(argSize?==?0)?{?? ???????????????????? ???????????????????? ????????????????????argSize?=?getArgumentsAndReturnSizes(desc);?? ???????????????????? ???????????????????? ????????????????????i.intVal?=?argSize;?? ????????????????}?? ????????????????int?size;?? ????????????????if?(opcode?==?Opcodes.INVOKESTATIC)?{?? ????????????????????size?=?stackSize?-?(argSize?>>?2)?+?(argSize?&?0x03)?+?1;?? ????????????????}?else?{?? ????????????????????size?=?stackSize?-?(argSize?>>?2)?+?(argSize?&?0x03);?? ????????????????}?? ???????????????? ????????????????if?(size?>?maxStackSize)?{?? ????????????????????maxStackSize?=?size;?? ????????????????}?? ????????????????stackSize?=?size;?? ????????????}?? ????????}?? ???????? ????????if?(itf)?{?? ????????????if?(argSize?==?0)?{?? ????????????????argSize?=?getArgumentsAndReturnSizes(desc);?? ????????????????i.intVal?=?argSize;?? ????????????}?? ????????????code.put12(Opcodes.INVOKEINTERFACE,?i.index).put11(argSize?>>?2,?0);?? ????????}?else?{?? ????????????code.put12(opcode,?i.index);?? ????????}?? ????}?? }?? 通過注釋我們可以大概理解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完成了BCEL和SERP同樣的功能,但ASM? 只有30多k,而后兩者分別是350k和150k。apache真是越來越過氣了。? 讓我們來看一個ASM的簡單例子Helloworld.java,它生成一個Example類和一個main方法,main方法打印"Hello world!"語句:?
Java代碼??
import?java.io.FileOutputStream;?? import?java.io.PrintStream;?? ?? import?org.objectweb.asm.ClassWriter;?? import?org.objectweb.asm.MethodVisitor;?? import?org.objectweb.asm.Opcodes;?? import?org.objectweb.asm.Type;?? import?org.objectweb.asm.commons.GeneratorAdapter;?? import?org.objectweb.asm.commons.Method;?? ?? public?class?Helloworld?extends?ClassLoader?implements?Opcodes?{?? ?? ??public?static?void?main(final?String?args[])?throws?Exception?{?? ?? ???? ???? ?? ????ClassWriter?cw?=?new?ClassWriter(0);?? ????cw.visit(V1_1,?ACC_PUBLIC,?"Example",?null,?"java/lang/Object",?null);?? ????MethodVisitor?mw?=?cw.visitMethod(ACC_PUBLIC,?"<init>",?"()V",?null,?? ????????null);?? ????mw.visitVarInsn(ALOAD,?0);?? ????mw.visitMethodInsn(INVOKESPECIAL,?"java/lang/Object",?"<init>",?"()V");?? ????mw.visitInsn(RETURN);?? ????mw.visitMaxs(1,?1);?? ????mw.visitEnd();?? ????mw?=?cw.visitMethod(ACC_PUBLIC?+?ACC_STATIC,?"main",?? ????????"([Ljava/lang/String;)V",?null,?null);?? ????mw.visitFieldInsn(GETSTATIC,?"java/lang/System",?"out",?? ????????"Ljava/io/PrintStream;");?? ????mw.visitLdcInsn("Hello?world!");?? ????mw.visitMethodInsn(INVOKEVIRTUAL,?"java/io/PrintStream",?"println",?? ????????"(Ljava/lang/String;)V");?? ????mw.visitInsn(RETURN);?? ????mw.visitMaxs(2,?2);?? ????mw.visitEnd();?? ????byte[]?code?=?cw.toByteArray();?? ????FileOutputStream?fos?=?new?FileOutputStream("Example.class");?? ????fos.write(code);?? ????fos.close();?? ????Helloworld?loader?=?new?Helloworld();?? ????Class?exampleClass?=?loader?? ????????.defineClass("Example",?code,?0,?code.length);?? ????exampleClass.getMethods()[0].invoke(null,?new?Object[]?{?null?});?? ?? ???? ???? ???? ?? ????cw?=?new?ClassWriter(ClassWriter.COMPUTE_MAXS);?? ????cw.visit(V1_1,?ACC_PUBLIC,?"Example",?null,?"java/lang/Object",?null);?? ????Method?m?=?Method.getMethod("void?<init>?()");?? ????GeneratorAdapter?mg?=?new?GeneratorAdapter(ACC_PUBLIC,?m,?null,?null,?? ????????cw);?? ????mg.loadThis();?? ????mg.invokeConstructor(Type.getType(Object.class),?m);?? ????mg.returnValue();?? ????mg.endMethod();?? ????m?=?Method.getMethod("void?main?(String[])");?? ????mg?=?new?GeneratorAdapter(ACC_PUBLIC?+?ACC_STATIC,?m,?null,?null,?cw);?? ????mg.getStatic(Type.getType(System.class),?"out",?Type?? ????????.getType(PrintStream.class));?? ????mg.push("Hello?world!");?? ????mg.invokeVirtual(Type.getType(PrintStream.class),?Method?? ????????.getMethod("void?println?(String)"));?? ????mg.returnValue();?? ????mg.endMethod();?? ????cw.visitEnd();?? ????code?=?cw.toByteArray();?? ????loader?=?new?Helloworld();?? ????exampleClass?=?loader.defineClass("Example",?code,?0,?code.length);?? ????exampleClass.getMethods()[0].invoke(null,?new?Object[]?{?null?});?? ??}?? }?? 我們看到上面的例子分別使用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代碼??
public?class?MyClass?{?? ?? ??public?void?print()?{?? ????System.out.println("I'm?in?MyClass.print!");?? ??}?? ?? }?? Main.java:?
Java代碼??
import?java.lang.reflect.Method;?? import?net.sf.cglib.proxy.Enhancer;?? import?net.sf.cglib.proxy.MethodInterceptor;?? import?net.sf.cglib.proxy.MethodProxy;?? ?? public?class?Main?{?? ?? ??public?static?void?main(String[]?args)?{?? ?? ????Enhancer?enhancer?=?new?Enhancer();?? ????enhancer.setSuperclass(MyClass.class);?? ????enhancer.setCallback(new?MethodInterceptorImpl());?? ????MyClass?my?=?(MyClass)?enhancer.create();?? ????my.print();?? ??}?? ?? ??private?static?class?MethodInterceptorImpl?implements?MethodInterceptor?{?? ????public?Object?intercept(Object?obj,?Method?method,?Object[]?args,?? ????????MethodProxy?proxy)?throws?Throwable?{?? ?????? ??????System.out.println(method?+?"?intercepted!");?? ?? ??????proxy.invokeSuper(obj,?args);?? ??????return?null;?? ????}?? ??}?? }?? 打印結果為:?
Java代碼??
public?void?MyClass.print()?intercepted!?? 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
轉載于:https://www.cnblogs.com/scott19820130/p/4639227.html
總結
以上是生活随笔 為你收集整理的[转载] 深入了解Java ClassLoader、Bytecode 、ASM、cglib 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。