日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

JVM插桩之四:Java动态代理机制的对比(JDK和CGLIB,Javassist,ASM)

發布時間:2024/1/23 java 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM插桩之四:Java动态代理机制的对比(JDK和CGLIB,Javassist,ASM) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、class文件簡介及加載

? ??Java編譯器編譯好Java文件之后,產生.class 文件在磁盤中。這種class文件是二進制文件,內容是只有JVM虛擬機能夠識別的機器碼。JVM虛擬機讀取字節碼文件,取出二進制數據,加載到內存中,解析.class文件內的信息,生成對應的Class對象:

class字節碼文件是根據JVM虛擬機規范中規定的字節碼組織規則生成的、具體class文件是怎樣組織類信息的,可以參考 此博文:深入理解Java Class文件格式系列。或者是Java虛擬機規范。

下面通過一段代碼演示手動加載 class文件字節碼到系統內,轉換成class對象,然后再實例化的過程:

a. 定義一個 Programmer類:

package com.dxz.chama.sample;public class Programmer {public void code() {System.out.println("I'm a Programmer,Just Coding.....");} }

b. 自定義一個類加載器:

package com.dxz.chama.sample;public class MyClassLoader extends ClassLoader {public Class<?> defineMyClass(byte[] b, int off, int len) {return super.defineClass(b, off, len);} }

c. 然后編譯成Programmer.class文件,在程序中讀取字節碼,然后轉換成相應的class對象,再實例化:

package com.dxz.chama.sample;import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException;public class MyTest {public static void main(String[] args) throws IOException {//讀取本地的class文件內的字節碼,轉換成字節碼數組File file = new File(".");InputStream input = new FileInputStream(file.getCanonicalPath() + "\\target\\classes\\com\\dxz\\chama\\sample\\Programmer.class");byte[] result = new byte[1024];int count = input.read(result);//使用自定義的類加載器將byte字節碼數組轉換為對應的class對象MyClassLoader loader = new MyClassLoader();Class clazz = loader.defineMyClass(result, 0, count);//測試加載是否成功,打印class對象的名稱System.out.println(clazz.getCanonicalName());try {// 實例化一個Programmer對象Object o = clazz.newInstance(0;// 調用Programmer的code方法clazz.getMethod("code", null).invoke(o, null);} catch (IllegalAccessException | InstantiationException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {e.printStackTrace();}} }

運行結果:

以上代碼演示了,通過字節碼加載成class 對象的能力,下面看一下在代碼中如何生成class文件的字節碼。

二、在運行期的代碼中生成二進制字節碼

???由于JVM通過字節碼的二進制信息加載類的,那么,如果我們在運行期系統中,遵循Java編譯系統組織.class文件的格式和結構,生成相應的二進制數據,然后再把這個二進制數據加載轉換成對應的類,這樣,就完成了在代碼中,動態創建一個類的能力了。

在運行時期可以按照Java虛擬機規范對class文件的組織規則生成對應的二進制字節碼。當前有很多開源框架可以完成這些功能,如ASMJavassist(Java Programming Assistant)

ASM、Javassist(Java Programming Assistant)比較

Javassist:

Javassist(Java編程助手)使Java字節碼操作變得簡單。它是一個用于在Java中編輯字節碼的類庫;它允許Java程序在運行時定義一個新類,并在JVM加載時修改類文件。不同于其他類似的字節碼編輯器。

Javassist提供兩個級別的API:源代碼級別和字節碼級別。如果用戶使用源級API,他們可以編輯一個類文件而不知道Java字節碼的規范。整個API只設計了Java語言的詞匯表。您甚至可以以源文本的形式指定插入的字節碼;Javassist可以動態編譯它。另一方面,字節碼級API允許用戶像其他編輯器一樣直接編輯類文件。

Javassist允許檢查、編輯和創建Java二進制類。

Javassist并不是唯一使用字節碼的庫,但它確實有一個特性,特別是它使它成為一個很好的用于實驗類工作的起點:您可以使用Javassist來修改Java類的字節碼,而不必學習字節碼或Java虛擬機(JVM)架構的任何內容。

面向方面的編程:Javassist可以是一個很好的工具,用于向類中添加新方法,以及在調用者和被調用者端插入前后切面。

反射:Javassist的一個應用是運行時反射;Javassist使Java程序能夠使用一個Meta對象來控制基類對象上的方法調用。不需要專門的編譯器或虛擬機。

Javassist還提供了用于直接編輯類文件的底層API。要使用這個API級別,需要了解Java字節碼和類文件格式的詳細知識,同時這種API級別允許您對類文件進行任何類型的修改。

ASM:?

ASM是一個通用的Java字節碼操作和分析框架。它可以直接以二進制形式修改現有類或動態生成類。提供了通用的轉換和分析算法,可以方便地組裝自定義的復雜轉換和代碼分析工具。

ASM提供與其他字節碼框架類似的功能,但它的重點是簡單的使用和性能。因為它的設計和實現是盡可能小和快,所以它非常有吸引力用于動態系統*。

ASM是一種Java類操作工具,用于動態生成和操作Java類,這是實現可適應系統的有用技術。與現有的等效工具相比,ASM基于一種新的方法,即使用“訪問者”設計模式,而不顯式地用對象表示被訪問的樹。對于大多數實際需要,這種新方法比現有工具的性能要好得多。

Javassist和ASM之間的比較:

?與ASM中的實際字節碼操作相比,Javassist源代碼級API更易于使用。

?Javassist在復雜的字節碼級操作上提供了更高級別的抽象層。Javassist源代碼級API需要的實際字節碼知識很少或根本不需要,因此實現起來更容易、更快。

?Javassist使用反射機制,這使得它比運行時使用類工作技術的ASM慢。

?總體上,ASM比Javassist更快,性能更好。Javassist使用Java源代碼的簡化版本,然后將其編譯成字節碼。這使得javassist非常容易使用,但是它也將字節碼的使用限制在javassist源代碼的限制范圍內。

總之,如果有人需要更簡單的方法來動態地操縱或創建Java類,則應該使用Javassist API,而性能是關鍵問題的ASM庫應該被使用。

Table 1. Class構建時間

Framework? ?First time? ? ?Later times
Javassist? ? ? 257? ? ? ? ? ? ? 5.2
BCEL? ? ? ? ? ?473? ? ? ? ? ? ? ?5.5
ASM? ? ? ? ? ? 62.4? ? ? ? ? ? ? 1.1
表1的結果表明,ASM確實比其他框架更快地實現了它的消耗時間,而且這一優勢既適用于啟動時間,也適用于重復使用。

  幾種動態編程方法相比較,在性能上Javassist高于反射,但低于ASM,因為Javassist增加了一層抽象。在實現成本上Javassist和反射都很低,而ASM由于直接操作字節碼,相比Javassist源碼級別的api實現成本高很多。幾個方法有自己的應用場景,比如Kryo使用的是ASM,追求性能的最大化。而NBeanCopyUtil采用的是Javassist,在對象拷貝的性能上也已經明顯高于其他的庫,并保持高易用性。實際項目中推薦先用Javassist實現原型,若在性能測試中發現Javassist成為了性能瓶頸,再考慮使用其他字節碼操作方法做優化

Java字節碼生成開源框架介紹--ASM:

ASM 是一個 Java 字節碼操控框架。它能夠以二進制形式修改已有類或者動態生成類。ASM 可以直接產生二進制 class 文件,也可以在類被加載入 Java 虛擬機之前動態改變類行為。ASM 從類文件中讀入信息后,能夠改變類行為,分析類信息,甚至能夠根據用戶要求生成新類。

不過ASM在創建class字節碼的過程中,操縱的級別是底層JVM的匯編指令級別,這要求ASM使用者要對class組織結構和JVM匯編指令有一定的了解。

下面通過ASM 生成下面類Programmer的class字節碼:

使用ASM框架提供了ClassWriter 接口,通過訪問者模式進行動態創建class字節碼,看下面的例子:

package com.dxz.chama.sample;import java.io.File; import java.io.FileOutputStream; import java.io.IOException;import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes;public class MyGeneratoro {public static void main(String[] args) throws IOException {System.out.println();ClassWriter classWriter = new ClassWriter(0);//通過visit方法確定類的頭部信息classWriter.visit(Opcodes.V1_7, // java版本Opcodes.ACC_PUBLIC, //類修飾符"Programmer", //類的全限定名null, "java/lang/Object", null);//創建構造函數MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);mv.visitCode();mv.visitVarInsn(Opcodes.ALOAD, 0);mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");mv.visitInsn(Opcodes.RETURN);mv.visitMaxs(1, 1);mv.visitEnd();// 定義code方法MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code", "()V", null, null);methodVisitor.visitCode();methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");methodVisitor.visitLdcInsn("I'm a Programmer, Just Coding.....");methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");methodVisitor.visitInsn(Opcodes.RETURN);methodVisitor.visitMaxs(2, 2);methodVisitor.visitEnd();classWriter.visitEnd();// 使classWriter類已經完成// 將classWriter轉換成字節數組寫到文件里面去byte[] data = classWriter.toByteArray();File file = new File("D://Programmer.class");FileOutputStream fout = new FileOutputStream(file);fout.write(data);fout.close();} }

?運行下,? ? ?上述的代碼執行過后,用Java反編譯工具(如JD_GUI)打開D盤下生成的Programmer.class,可以看到以下信息:

再用上面我們定義的類加載器將這個class文件加載到內存中,然后創建class對象,并且實例化一個對象,調用code方法,會看到下面的結果:

三、Java字節碼生成開源框架介紹--Javassist:

Javassist是一個開源的分析、編輯和創建Java字節碼的類庫。是由東京工業大學的數學和計算機科學系的 Shigeru Chiba (千葉 滋)所創建的。它已加入了開放源代碼JBoss 應用服務器項目,通過使用Javassist對字節碼操作為JBoss實現動態AOP框架。javassist是jboss的一個子項目,其主要的優點,在于簡單,而且快速。直接使用java編碼的形式,而不需要了解虛擬機指令,就能動態改變類的結構,或者動態生成類。

下面通過Javassist創建上述的Programmer類:

package com.dxz.chama.sample.javassist;import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.CtNewMethod;public class MyGenerator {public static void main(String[] args) throws Exception {ClassPool pool = ClassPool.getDefault();// 創建Programmer類CtClass cc = pool.makeClass("com.dxz.chama.sample.Programmer");// 定義code方法CtMethod method = CtNewMethod.make("public void code(){}", cc);// 插入方法代碼method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding by javassist.....\");");cc.addMethod(method);// 保存生成的字節碼cc.writeFile("d://temp");} }

通過JD-gui反編譯工具打開Programmer.class 可以看到以下代碼:

四、jdk的靜態代理的基本構成:

??????? 代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色。其中:Subject角色負責定義RealSubject和Proxy角色應該實現的接口;RealSubject角色用來真正完成業務服務功能;Proxy角色負責將自身的Request請求,調用realsubject對應的request功能來實現業務功能,自己不真正做業務。

當在代碼階段規定這種代理關系,Proxy類通過編譯器編譯成class文件,當系統運行時,此class已經存在了。這種靜態的代理模式固然在訪問無法訪問的資源,增強現有的接口業務功能方面有很大的優點,但是大量使用這種靜態代理,會使我們系統內的類的規模增大,并且不易維護;并且由于Proxy和RealSubject的功能 本質上是相同的,Proxy只是起到了中介的作用,這種代理在系統中的存在,導致系統結構比較臃腫和松散。

?????? 為了解決這個問題,就有了動態地創建Proxy的想法:在運行狀態中,需要代理的地方,根據Subject 和RealSubject,動態地創建一個Proxy,用完之后,就會銷毀,這樣就可以避免了Proxy 角色的class在系統中冗雜的問題了。

下面以一個代理模式實例闡述這一問題:

將車站的售票服務抽象出一個接口TicketService,包含問詢,賣票,退票功能,車站類Station實現了TicketService接口,車票代售點StationProxy則實現了代理角色的功能,類圖如下所示。

通過上面動態生成的代碼,我們發現,其實現相當地麻煩在創造的過程中,含有太多的業務代碼。我們使用上述創建Proxy代理類的方式的初衷是減少系統代碼的冗雜度,但是上述做法卻增加了在動態創建代理類過程中的復雜度:手動地創建了太多的業務代碼,并且封裝性也不夠,完全不具有可拓展性和通用性。如果某個代理類的一些業務邏輯非常復雜,上述的動態創建代理的方式是非常不可取的!

五、jdk的動態InvocationHandler角色的由來

仔細思考代理模式中的代理Proxy角色。Proxy角色在執行代理業務的時候,無非是在調用真正業務之前或者之后做一些“額外”業務。

六、cglib 生成動態代理類的機制----通過類繼承:

?????? JDK中提供的生成動態代理類的機制有個鮮明的特點是: 某個類必須有實現的接口,而生成的代理類也只能代理某個類接口定義的方法,比如:如果上面例子的ElectricCar實現了繼承自兩個接口的方法外,另外實現了方法bee() ,則在產生的動態代理類中不會有這個方法了!更極端的情況是:如果某個類沒有實現接口,那么這個類就不能同JDK產生動態代理了!

???? 幸好我們有cglib。“CGLIB(Code Generation Library),是一個強大的,高性能,高質量的Code生成類庫,它可以在運行期擴展Java類與實現Java接口。”

cglib 創建某個類A的動態代理類的模式是:

1.查找A上的所有非final的public類型的方法定義;

2.將這些方法的定義轉換成字節碼;

3.將組成的字節碼轉換成相應的代理的class對象;

4.實現MethodInterceptor接口,用來處理對代理類上所有方法的請求(這個接口和JDK動態代理Invocationhandler的功能和角色是一樣的)

一個有趣的例子:定義一個Programmer類,一個Hacker類

package com.dxz.chama.sample.cglib; public class Programmer {public void code() {System.out.println("I'm a Programmer, Just Coding by cglib......");} } package com.dxz.chama.sample.cglib;import java.lang.reflect.Method;import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy;/** 實現了方法攔截器接口*/ public class Hacker implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("**** I am a hacker,Let's see what the poor programmer is doing Now...");proxy.invokeSuper(obj, args);System.out.println("**** Oh, what a poor programmer......");return null;} }

test類

package com.dxz.chama.sample.cglib;import net.sf.cglib.proxy.Enhancer;public class Test {public static void maian(String[] args) {Programmer progammer = new Programmer();Hacker hacker = new Hacker();// cglib中加強器,用來創建動態代理Enhancer enhancer = new Enhancer(0;// 設置要創建動態代理的類enhancer.setSuperclass(progammer.getClass());// 設置回調,這里相當于是對于代理類上所有方法的調用,都會調用CallBack,而Callback則需要實行intercept()方法進行攔截enhancer.setCallback(hacker);Programmer proxy = (Programmer) enhancer.create(0;proxy.code(0;} }

程序執行結果:

我們看看通過cglib生成的class文件內容:

import java.lang.reflect.Method; import net.sf.cglib.core.ReflectUtils; import net.sf.cglib.core.Signature; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Factory; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class Programmer fa7aa2cd extends Programmer implements Factory { //......省略 private MethodInterceptor CGLIB$CALLBACK_0; // Enchaner傳入的methodInterceptor // ....省略 public final void code() { MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0; if (tmp4_1 == null) { tmp4_1; CGLIB$BIND_CALLBACKS(this);//若callback 不為空,則調用methodInterceptor 的intercept()方法 } if (this.CGLIB$CALLBACK_0 != null) return; //如果沒有設置callback回調函數,則默認執行父類的方法 super.code(); } //....后續省略 }

?

總結

以上是生活随笔為你收集整理的JVM插桩之四:Java动态代理机制的对比(JDK和CGLIB,Javassist,ASM)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。