日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java 动态代理机制分析及扩展--转

發(fā)布時(shí)間:2025/4/5 java 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 动态代理机制分析及扩展--转 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/#icomments

http://www.ibm.com/developerworks/cn/java/j-lo-proxy2/

?

引言

Java 動(dòng)態(tài)代理機(jī)制的出現(xiàn),使得 Java 開發(fā)人員不用手工編寫代理類,只要簡單地指定一組接口及委托類對(duì)象,便能動(dòng)態(tài)地獲得代理類。代理類會(huì)負(fù)責(zé)將所有的方法調(diào)用分派到委托對(duì)象上反射執(zhí)行,在分派執(zhí)行的過程中,開發(fā)人員還可以按需調(diào)整委托類對(duì)象及其功能,這是一套非常靈活有彈性的代理框架。通過閱讀本文,讀者將會(huì)對(duì) Java 動(dòng)態(tài)代理機(jī)制有更加深入的理解。本文首先從 Java 動(dòng)態(tài)代理的運(yùn)行機(jī)制和特點(diǎn)出發(fā),對(duì)其代碼進(jìn)行了分析,推演了動(dòng)態(tài)生成類的內(nèi)部實(shí)現(xiàn)。

代理:設(shè)計(jì)模式

代理是一種常用的設(shè)計(jì)模式,其目的就是為其他對(duì)象提供一個(gè)代理以控制對(duì)某個(gè)對(duì)象的訪問。代理類負(fù)責(zé)為委托類預(yù)處理消息,過濾消息并轉(zhuǎn)發(fā)消息,以及進(jìn)行消息被委托類執(zhí)行后的后續(xù)處理。

圖 1. 代理模式

為了保持行為的一致性,代理類和委托類通常會(huì)實(shí)現(xiàn)相同的接口,所以在訪問者看來兩者沒有絲毫的區(qū)別。通過代理類這中間一層,能有效控制對(duì)委托類對(duì)象的直接訪問,也可以很好地隱藏和保護(hù)委托類對(duì)象,同時(shí)也為實(shí)施不同控制策略預(yù)留了空間,從而在設(shè)計(jì)上獲得了更大的靈活性。Java 動(dòng)態(tài)代理機(jī)制以巧妙的方式近乎完美地實(shí)踐了代理模式的設(shè)計(jì)理念。

相關(guān)的類和接口

要了解 Java 動(dòng)態(tài)代理的機(jī)制,首先需要了解以下相關(guān)的類或接口:

  • java.lang.reflect.Proxy:這是 Java 動(dòng)態(tài)代理機(jī)制的主類,它提供了一組靜態(tài)方法來為一組接口動(dòng)態(tài)地生成代理類及其對(duì)象。
    清單 1. Proxy 的靜態(tài)方法
    // 方法 1: 該方法用于獲取指定代理對(duì)象所關(guān)聯(lián)的調(diào)用處理器 static InvocationHandler getInvocationHandler(Object proxy) // 方法 2:該方法用于獲取關(guān)聯(lián)于指定類裝載器和一組接口的動(dòng)態(tài)代理類的類對(duì)象 static Class getProxyClass(ClassLoader loader, Class[] interfaces) // 方法 3:該方法用于判斷指定類對(duì)象是否是一個(gè)動(dòng)態(tài)代理類 static boolean isProxyClass(Class cl) // 方法 4:該方法用于為指定類裝載器、一組接口及調(diào)用處理器生成動(dòng)態(tài)代理類實(shí)例 static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
  • java.lang.reflect.InvocationHandler:這是調(diào)用處理器接口,它自定義了一個(gè) invoke 方法,用于集中處理在動(dòng)態(tài)代理類對(duì)象上的方法調(diào)用,通常在該方法中實(shí)現(xiàn)對(duì)委托類的代理訪問。
    清單 2. InvocationHandler 的核心方法
    // 該方法負(fù)責(zé)集中處理動(dòng)態(tài)代理類上的所有方法調(diào)用。第一個(gè)參數(shù)既是代理類實(shí)例,第二個(gè)參數(shù)是被調(diào)用的方法對(duì)象 // 第三個(gè)方法是調(diào)用參數(shù)。調(diào)用處理器根據(jù)這三個(gè)參數(shù)進(jìn)行預(yù)處理或分派到委托類實(shí)例上發(fā)射執(zhí)行 Object invoke(Object proxy, Method method, Object[] args)

    每次生成動(dòng)態(tài)代理類對(duì)象時(shí)都需要指定一個(gè)實(shí)現(xiàn)了該接口的調(diào)用處理器對(duì)象(參見 Proxy 靜態(tài)方法 4 的第三個(gè)參數(shù))。

  • java.lang.ClassLoader:這是類裝載器類,負(fù)責(zé)將類的字節(jié)碼裝載到 Java 虛擬機(jī)(JVM)中并為其定義類對(duì)象,然后該類才能被使用。Proxy 靜態(tài)方法生成動(dòng)態(tài)代理類同樣需要通過類裝載器來進(jìn)行裝載才能使用,它與普通類的唯一區(qū)別就是其字節(jié)碼是由 JVM 在運(yùn)行時(shí)動(dòng)態(tài)生成的而非預(yù)存在于任何一個(gè) .class 文件中。

    每次生成動(dòng)態(tài)代理類對(duì)象時(shí)都需要指定一個(gè)類裝載器對(duì)象(參見 Proxy 靜態(tài)方法 4 的第一個(gè)參數(shù))

代理機(jī)制及其特點(diǎn)

首先讓我們來了解一下如何使用 Java 動(dòng)態(tài)代理。具體有如下四步驟:

  • 通過實(shí)現(xiàn) InvocationHandler 接口創(chuàng)建自己的調(diào)用處理器;
  • 通過為 Proxy 類指定 ClassLoader 對(duì)象和一組 interface 來創(chuàng)建動(dòng)態(tài)代理類;
  • 通過反射機(jī)制獲得動(dòng)態(tài)代理類的構(gòu)造函數(shù),其唯一參數(shù)類型是調(diào)用處理器接口類型;
  • 通過構(gòu)造函數(shù)創(chuàng)建動(dòng)態(tài)代理類實(shí)例,構(gòu)造時(shí)調(diào)用處理器對(duì)象作為參數(shù)被傳入。
  • 清單 3. 動(dòng)態(tài)代理對(duì)象創(chuàng)建過程
    // InvocationHandlerImpl 實(shí)現(xiàn)了 InvocationHandler 接口,并能實(shí)現(xiàn)方法調(diào)用從代理類到委托類的分派轉(zhuǎn)發(fā) // 其內(nèi)部通常包含指向委托類實(shí)例的引用,用于真正執(zhí)行分派轉(zhuǎn)發(fā)過來的方法調(diào)用 InvocationHandler handler = new InvocationHandlerImpl(..); // 通過 Proxy 為包括 Interface 接口在內(nèi)的一組接口動(dòng)態(tài)創(chuàng)建代理類的類對(duì)象 Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); // 通過反射從生成的類對(duì)象獲得構(gòu)造函數(shù)對(duì)象 Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); // 通過構(gòu)造函數(shù)對(duì)象創(chuàng)建動(dòng)態(tài)代理類實(shí)例 Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

    實(shí)際使用過程更加簡單,因?yàn)?Proxy 的靜態(tài)方法 newProxyInstance 已經(jīng)為我們封裝了步驟 2 到步驟 4 的過程,所以簡化后的過程如下

    清單 4. 簡化的動(dòng)態(tài)代理對(duì)象創(chuàng)建過程
    // InvocationHandlerImpl 實(shí)現(xiàn)了 InvocationHandler 接口,并能實(shí)現(xiàn)方法調(diào)用從代理類到委托類的分派轉(zhuǎn)發(fā) InvocationHandler handler = new InvocationHandlerImpl(..); // 通過 Proxy 直接創(chuàng)建動(dòng)態(tài)代理類實(shí)例 Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, new Class[] { Interface.class }, handler );

    接下來讓我們來了解一下 Java 動(dòng)態(tài)代理機(jī)制的一些特點(diǎn)。

    首先是動(dòng)態(tài)生成的代理類本身的一些特點(diǎn)。1)包:如果所代理的接口都是 public 的,那么它將被定義在頂層包(即包路徑為空),如果所代理的接口中有非 public 的接口(因?yàn)榻涌诓荒鼙欢x為 protect 或 private,所以除 public 之外就是默認(rèn)的 package 訪問級(jí)別),那么它將被定義在該接口所在包(假設(shè)代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理類所在的包就是 com.ibm.developerworks),這樣設(shè)計(jì)的目的是為了最大程度的保證動(dòng)態(tài)代理類不會(huì)因?yàn)榘芾淼膯栴}而無法被成功定義并訪問;2)類修飾符:該代理類具有 final 和 public 修飾符,意味著它可以被所有的類訪問,但是不能被再度繼承;3)類名:格式是“$ProxyN”,其中 N 是一個(gè)逐一遞增的阿拉伯?dāng)?shù)字,代表 Proxy 類第 N 次生成的動(dòng)態(tài)代理類,值得注意的一點(diǎn)是,并不是每次調(diào)用 Proxy 的靜態(tài)方法創(chuàng)建動(dòng)態(tài)代理類都會(huì)使得 N 值增加,原因是如果對(duì)同一組接口(包括接口排列的順序相同)試圖重復(fù)創(chuàng)建動(dòng)態(tài)代理類,它會(huì)很聰明地返回先前已經(jīng)創(chuàng)建好的代理類的類對(duì)象,而不會(huì)再嘗試去創(chuàng)建一個(gè)全新的代理類,這樣可以節(jié)省不必要的代碼重復(fù)生成,提高了代理類的創(chuàng)建效率。4)類繼承關(guān)系:該類的繼承關(guān)系如圖:

    圖 2. 動(dòng)態(tài)代理類的繼承圖

    由圖可見,Proxy 類是它的父類,這個(gè)規(guī)則適用于所有由 Proxy 創(chuàng)建的動(dòng)態(tài)代理類。而且該類還實(shí)現(xiàn)了其所代理的一組接口,這就是為什么它能夠被安全地類型轉(zhuǎn)換到其所代理的某接口的根本原因。

    接下來讓我們了解一下代理類實(shí)例的一些特點(diǎn)。每個(gè)實(shí)例都會(huì)關(guān)聯(lián)一個(gè)調(diào)用處理器對(duì)象,可以通過 Proxy 提供的靜態(tài)方法 getInvocationHandler 去獲得代理類實(shí)例的調(diào)用處理器對(duì)象。在代理類實(shí)例上調(diào)用其代理的接口中所聲明的方法時(shí),這些方法最終都會(huì)由調(diào)用處理器的 invoke 方法執(zhí)行,此外,值得注意的是,代理類的根類 java.lang.Object 中有三個(gè)方法也同樣會(huì)被分派到調(diào)用處理器的 invoke 方法執(zhí)行,它們是 hashCode,equals 和 toString,可能的原因有:一是因?yàn)檫@些方法為 public 且非 final 類型,能夠被代理類覆蓋;二是因?yàn)檫@些方法往往呈現(xiàn)出一個(gè)類的某種特征屬性,具有一定的區(qū)分度,所以為了保證代理類與委托類對(duì)外的一致性,這三個(gè)方法也應(yīng)該被分派到委托類執(zhí)行。當(dāng)代理的一組接口有重復(fù)聲明的方法且該方法被調(diào)用時(shí),代理類總是從排在最前面的接口中獲取方法對(duì)象并分派給調(diào)用處理器,而無論代理類實(shí)例是否正在以該接口(或繼承于該接口的某子接口)的形式被外部引用,因?yàn)樵诖眍悆?nèi)部無法區(qū)分其當(dāng)前的被引用類型。

    接著來了解一下被代理的一組接口有哪些特點(diǎn)。首先,要注意不能有重復(fù)的接口,以避免動(dòng)態(tài)代理類代碼生成時(shí)的編譯錯(cuò)誤。其次,這些接口對(duì)于類裝載器必須可見,否則類裝載器將無法鏈接它們,將會(huì)導(dǎo)致類定義失敗。再次,需被代理的所有非 public 的接口必須在同一個(gè)包中,否則代理類生成也會(huì)失敗。最后,接口的數(shù)目不能超過 65535,這是 JVM 設(shè)定的限制。

    最后再來了解一下異常處理方面的特點(diǎn)。從調(diào)用處理器接口聲明的方法中可以看到理論上它能夠拋出任何類型的異常,因?yàn)樗械漠惓6祭^承于 Throwable 接口,但事實(shí)是否如此呢?答案是否定的,原因是我們必須遵守一個(gè)繼承原則:即子類覆蓋父類或?qū)崿F(xiàn)父接口的方法時(shí),拋出的異常必須在原方法支持的異常列表之內(nèi)。所以雖然調(diào)用處理器理論上講能夠,但實(shí)際上往往受限制,除非父接口中的方法支持拋 Throwable 異常。那么如果在 invoke 方法中的確產(chǎn)生了接口方法聲明中不支持的異常,那將如何呢?放心,Java 動(dòng)態(tài)代理類已經(jīng)為我們?cè)O(shè)計(jì)好了解決方法:它將會(huì)拋出 UndeclaredThrowableException 異常。這個(gè)異常是一個(gè) RuntimeException 類型,所以不會(huì)引起編譯錯(cuò)誤。通過該異常的 getCause 方法,還可以獲得原來那個(gè)不受支持的異常對(duì)象,以便于錯(cuò)誤診斷。

    代碼是最好的老師

    機(jī)制和特點(diǎn)都介紹過了,接下來讓我們通過源代碼來了解一下 Proxy 到底是如何實(shí)現(xiàn)的。

    首先記住 Proxy 的幾個(gè)重要的靜態(tài)變量:

    清單 5. Proxy 的重要靜態(tài)變量
    // 映射表:用于維護(hù)類裝載器對(duì)象到其對(duì)應(yīng)的代理類緩存 private static Map loaderToCache = new WeakHashMap(); // 標(biāo)記:用于標(biāo)記一個(gè)動(dòng)態(tài)代理類正在被創(chuàng)建中 private static Object pendingGenerationMarker = new Object(); // 同步表:記錄已經(jīng)被創(chuàng)建的動(dòng)態(tài)代理類類型,主要被方法 isProxyClass 進(jìn)行相關(guān)的判斷 private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap()); // 關(guān)聯(lián)的調(diào)用處理器引用 protected InvocationHandler h;

    然后,來看一下 Proxy 的構(gòu)造方法:

    清單 6. Proxy 構(gòu)造方法
    // 由于 Proxy 內(nèi)部從不直接調(diào)用構(gòu)造函數(shù),所以 private 類型意味著禁止任何調(diào)用 private Proxy() {} // 由于 Proxy 內(nèi)部從不直接調(diào)用構(gòu)造函數(shù),所以 protected 意味著只有子類可以調(diào)用 protected Proxy(InvocationHandler h) {this.h = h;}

    接著,可以快速瀏覽一下 newProxyInstance 方法,因?yàn)槠湎喈?dāng)簡單:

    清單 7. Proxy 靜態(tài)方法 newProxyInstance
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // 檢查 h 不為空,否則拋異常if (h == null) { throw new NullPointerException(); } // 獲得與制定類裝載器和一組接口相關(guān)的代理類類型對(duì)象Class cl = getProxyClass(loader, interfaces); // 通過反射獲取構(gòu)造函數(shù)對(duì)象并生成代理類實(shí)例try { Constructor cons = cl.getConstructor(constructorParams); return (Object) cons.newInstance(new Object[] { h }); } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } catch (IllegalAccessException e) { throw new InternalError(e.toString()); } catch (InstantiationException e) { throw new InternalError(e.toString()); } catch (InvocationTargetException e) { throw new InternalError(e.toString()); } }

    由此可見,動(dòng)態(tài)代理真正的關(guān)鍵是在 getProxyClass 方法,該方法負(fù)責(zé)為一組接口動(dòng)態(tài)地生成代理類類型對(duì)象。在該方法內(nèi)部,您將能看到 Proxy 內(nèi)的各路英雄(靜態(tài)變量)悉數(shù)登場。有點(diǎn)迫不及待了么?那就讓我們一起走進(jìn) Proxy 最最神秘的殿堂去欣賞一番吧。該方法總共可以分為四個(gè)步驟:

  • 對(duì)這組接口進(jìn)行一定程度的安全檢查,包括檢查接口類對(duì)象是否對(duì)類裝載器可見并且與類裝載器所能識(shí)別的接口類對(duì)象是完全相同的,還會(huì)檢查確保是 interface 類型而不是 class 類型。這個(gè)步驟通過一個(gè)循環(huán)來完成,檢查通過后將會(huì)得到一個(gè)包含所有接口名稱的字符串?dāng)?shù)組,記為?String[] interfaceNames。總體上這部分實(shí)現(xiàn)比較直觀,所以略去大部分代碼,僅保留留如何判斷某類或接口是否對(duì)特定類裝載器可見的相關(guān)代碼。
    清單 8. 通過 Class.forName 方法判接口的可見性
    try { // 指定接口名字、類裝載器對(duì)象,同時(shí)制定 initializeBoolean 為 false 表示無須初始化類// 如果方法返回正常這表示可見,否則會(huì)拋出 ClassNotFoundException 異常表示不可見interfaceClass = Class.forName(interfaceName, false, loader); } catch (ClassNotFoundException e) { }
  • 從 loaderToCache 映射表中獲取以類裝載器對(duì)象為關(guān)鍵字所對(duì)應(yīng)的緩存表,如果不存在就創(chuàng)建一個(gè)新的緩存表并更新到 loaderToCache。緩存表是一個(gè) HashMap 實(shí)例,正常情況下它將存放鍵值對(duì)(接口名字列表,動(dòng)態(tài)生成的代理類的類對(duì)象引用)。當(dāng)代理類正在被創(chuàng)建時(shí)它會(huì)臨時(shí)保存(接口名字列表,pendingGenerationMarker)。標(biāo)記 pendingGenerationMarke 的作用是通知后續(xù)的同類請(qǐng)求(接口數(shù)組相同且組內(nèi)接口排列順序也相同)代理類正在被創(chuàng)建,請(qǐng)保持等待直至創(chuàng)建完成。
    清單 9. 緩存表的使用
    do { // 以接口名字列表作為關(guān)鍵字獲得對(duì)應(yīng) cache 值Object value = cache.get(key); if (value instanceof Reference) { proxyClass = (Class) ((Reference) value).get(); } if (proxyClass != null) { // 如果已經(jīng)創(chuàng)建,直接返回return proxyClass; } else if (value == pendingGenerationMarker) { // 代理類正在被創(chuàng)建,保持等待try { cache.wait(); } catch (InterruptedException e) { } // 等待被喚醒,繼續(xù)循環(huán)并通過二次檢查以確保創(chuàng)建完成,否則重新等待continue; } else { // 標(biāo)記代理類正在被創(chuàng)建cache.put(key, pendingGenerationMarker); // break 跳出循環(huán)已進(jìn)入創(chuàng)建過程break; } while (true);
  • 動(dòng)態(tài)創(chuàng)建代理類的類對(duì)象。首先是確定代理類所在的包,其原則如前所述,如果都為 public 接口,則包名為空字符串表示頂層包;如果所有非 public 接口都在同一個(gè)包,則包名與這些接口的包名相同;如果有多個(gè)非 public 接口且不同包,則拋異常終止代理類的生成。確定了包后,就開始生成代理類的類名,同樣如前所述按格式“$ProxyN”生成。類名也確定了,接下來就是見證奇跡的發(fā)生 —— 動(dòng)態(tài)生成代理類:
    清單 10. 動(dòng)態(tài)生成代理類
    // 動(dòng)態(tài)地生成代理類的字節(jié)碼數(shù)組 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); try { // 動(dòng)態(tài)地定義新生成的代理類proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } // 把生成的代理類的類對(duì)象記錄進(jìn) proxyClasses 表 proxyClasses.put(proxyClass, null);

    由此可見,所有的代碼生成的工作都由神秘的 ProxyGenerator 所完成了,當(dāng)你嘗試去探索這個(gè)類時(shí),你所能獲得的信息僅僅是它位于并未公開的 sun.misc 包,有若干常量、變量和方法以完成這個(gè)神奇的代碼生成的過程,但是 sun 并沒有提供源代碼以供研讀。至于動(dòng)態(tài)類的定義,則由 Proxy 的 native 靜態(tài)方法 defineClass0 執(zhí)行。

  • 代碼生成過程進(jìn)入結(jié)尾部分,根據(jù)結(jié)果更新緩存表,如果成功則將代理類的類對(duì)象引用更新進(jìn)緩存表,否則清楚緩存表中對(duì)應(yīng)關(guān)鍵值,最后喚醒所有可能的正在等待的線程。
  • 走完了以上四個(gè)步驟后,至此,所有的代理類生成細(xì)節(jié)都已介紹完畢,剩下的靜態(tài)方法如 getInvocationHandler 和 isProxyClass 就顯得如此的直觀,只需通過查詢相關(guān)變量就可以完成,所以對(duì)其的代碼分析就省略了。

    代理類實(shí)現(xiàn)推演

    分析了 Proxy 類的源代碼,相信在讀者的腦海中會(huì)對(duì) Java 動(dòng)態(tài)代理機(jī)制形成一個(gè)更加清晰的理解,但是,當(dāng)探索之旅在 sun.misc.ProxyGenerator 類處嘎然而止,所有的神秘都匯聚于此時(shí),相信不少讀者也會(huì)對(duì)這個(gè) ProxyGenerator 類產(chǎn)生有類似的疑惑:它到底做了什么呢?它是如何生成動(dòng)態(tài)代理類的代碼的呢?誠然,這里也無法給出確切的答案。還是讓我們帶著這些疑惑,一起開始探索之旅吧。

    事物往往不像其看起來的復(fù)雜,需要的是我們能夠化繁為簡,這樣也許就能有更多撥云見日的機(jī)會(huì)。拋開所有想象中的未知而復(fù)雜的神秘因素,如果讓我們用最簡單的方法去實(shí)現(xiàn)一個(gè)代理類,唯一的要求是同樣結(jié)合調(diào)用處理器實(shí)施方法的分派轉(zhuǎn)發(fā),您的第一反應(yīng)將是什么呢?“聽起來似乎并不是很復(fù)雜”。的確,掐指算算所涉及的工作無非包括幾個(gè)反射調(diào)用,以及對(duì)原始類型數(shù)據(jù)的裝箱或拆箱過程,其他的似乎都已經(jīng)水到渠成。非常地好,讓我們整理一下思緒,一起來完成一次完整的推演過程吧。

    清單 11. 代理類中方法調(diào)用的分派轉(zhuǎn)發(fā)推演實(shí)現(xiàn)
    // 假設(shè)需代理接口 Simulator public interface Simulator { short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB; } // 假設(shè)代理類為 SimulatorProxy, 其類聲明將如下 final public class SimulatorProxy implements Simulator { // 調(diào)用處理器對(duì)象的引用protected InvocationHandler handler; // 以調(diào)用處理器為參數(shù)的構(gòu)造函數(shù)public SimulatorProxy(InvocationHandler handler){ this.handler = handler; } // 實(shí)現(xiàn)接口方法 simulate public short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB {// 第一步是獲取 simulate 方法的 Method 對(duì)象java.lang.reflect.Method method = null; try{ method = Simulator.class.getMethod( "simulate", new Class[] {int.class, long.class, String.class} );} catch(Exception e) { // 異常處理 1(略)} // 第二步是調(diào)用 handler 的 invoke 方法分派轉(zhuǎn)發(fā)方法調(diào)用Object r = null; try { r = handler.invoke(this, method, // 對(duì)于原始類型參數(shù)需要進(jìn)行裝箱操作new Object[] {new Integer(arg1), new Long(arg2), arg3});}catch(Throwable e) { // 異常處理 2(略)} // 第三步是返回結(jié)果(返回類型是原始類型則需要進(jìn)行拆箱操作)return ((Short)r).shortValue();} }

    模擬推演為了突出通用邏輯所以更多地關(guān)注正常流程,而淡化了錯(cuò)誤處理,但在實(shí)際中錯(cuò)誤處理同樣非常重要。從以上的推演中我們可以得出一個(gè)非常通用的結(jié)構(gòu)化流程:第一步從代理接口獲取被調(diào)用的方法對(duì)象,第二步分派方法到調(diào)用處理器執(zhí)行,第三步返回結(jié)果。在這之中,所有的信息都是可以已知的,比如接口名、方法名、參數(shù)類型、返回類型以及所需的裝箱和拆箱操作,那么既然我們手工編寫是如此,那又有什么理由不相信 ProxyGenerator 不會(huì)做類似的實(shí)現(xiàn)呢?至少這是一種比較可能的實(shí)現(xiàn)。

    接下來讓我們把注意力重新回到先前被淡化的錯(cuò)誤處理上來。在異常處理 1 處,由于我們有理由確保所有的信息如接口名、方法名和參數(shù)類型都準(zhǔn)確無誤,所以這部分異常發(fā)生的概率基本為零,所以基本可以忽略。而異常處理 2 處,我們需要思考得更多一些。回想一下,接口方法可能聲明支持一個(gè)異常列表,而調(diào)用處理器 invoke 方法又可能拋出與接口方法不支持的異常,再回想一下先前提及的 Java 動(dòng)態(tài)代理的關(guān)于異常處理的特點(diǎn),對(duì)于不支持的異常,必須拋 UndeclaredThrowableException 運(yùn)行時(shí)異常。所以通過再次推演,我們可以得出一個(gè)更加清晰的異常處理 2 的情況:

    清單 12. 細(xì)化的異常處理 2
    Object r = null; try { r = handler.invoke(this, method, new Object[] {new Integer(arg1), new Long(arg2), arg3}); } catch( ExceptionA e) { // 接口方法支持 ExceptionA,可以拋出throw e; } catch( ExceptionB e ) { // 接口方法支持 ExceptionB,可以拋出throw e; } catch(Throwable e) { // 其他不支持的異常,一律拋 UndeclaredThrowableException throw new UndeclaredThrowableException(e); }

    這樣我們就完成了對(duì)動(dòng)態(tài)代理類的推演實(shí)現(xiàn)。推演實(shí)現(xiàn)遵循了一個(gè)相對(duì)固定的模式,可以適用于任意定義的任何接口,而且代碼生成所需的信息都是可知的,那么有理由相信即使是機(jī)器自動(dòng)編寫的代碼也有可能延續(xù)這樣的風(fēng)格,至少可以保證這是可行的。

    美中不足

    誠然,Proxy 已經(jīng)設(shè)計(jì)得非常優(yōu)美,但是還是有一點(diǎn)點(diǎn)小小的遺憾之處,那就是它始終無法擺脫僅支持 interface 代理的桎梏,因?yàn)樗脑O(shè)計(jì)注定了這個(gè)遺憾。回想一下那些動(dòng)態(tài)生成的代理類的繼承關(guān)系圖,它們已經(jīng)注定有一個(gè)共同的父類叫 Proxy。Java 的繼承機(jī)制注定了這些動(dòng)態(tài)代理類們無法實(shí)現(xiàn)對(duì) class 的動(dòng)態(tài)代理,原因是多繼承在 Java 中本質(zhì)上就行不通。

    有很多條理由,人們可以否定對(duì) class 代理的必要性,但是同樣有一些理由,相信支持 class 動(dòng)態(tài)代理會(huì)更美好。接口和類的劃分,本就不是很明顯,只是到了 Java 中才變得如此的細(xì)化。如果只從方法的聲明及是否被定義來考量,有一種兩者的混合體,它的名字叫抽象類。實(shí)現(xiàn)對(duì)抽象類的動(dòng)態(tài)代理,相信也有其內(nèi)在的價(jià)值。此外,還有一些歷史遺留的類,它們將因?yàn)闆]有實(shí)現(xiàn)任何接口而從此與動(dòng)態(tài)代理永世無緣。如此種種,不得不說是一個(gè)小小的遺憾。

    但是,不完美并不等于不偉大,偉大是一種本質(zhì),Java 動(dòng)態(tài)代理就是佐例。

    ?

    本文希望將 Java 動(dòng)態(tài)代理機(jī)制從接口擴(kuò)展到類,使得類能夠享有與接口類似的動(dòng)態(tài)代理支持。

    設(shè)計(jì)及特點(diǎn)

    新擴(kuò)展的類名為 ProxyEx,將直接繼承于 java.lang.reflect.Proxy,也聲明了與原 Proxy 類中同名的 public 靜態(tài)方法,目的是保持與原代理機(jī)制在使用方法上的完全一致。

    圖 1. ProxyEx 類繼承圖

    與原代理機(jī)制最大的區(qū)別在于,動(dòng)態(tài)生成的代理類將不再從 Proxy 類繼承,改而繼承需被代理的類。由于 Java 的單繼承原則,擴(kuò)展代理機(jī)制所支持的類數(shù)目不得多于一個(gè),但它可以聲明實(shí)現(xiàn)若干接口。包管理的機(jī)制與原來相似,不支持一個(gè)以上的類和接口同時(shí)為非 public;如果僅有一個(gè)非 public 的類或接口,假設(shè)其包為 PackageA,則動(dòng)態(tài)生成的代理類將位于包 PackageA;否則將位于被代理的類所在的包。生成的代理類也被賦予 final 和 public 訪問屬性,且其命名規(guī)則類似地為“父類名 +ProxyN”(N 也是遞增的阿拉伯?dāng)?shù)字)。最后,在異常處理方面則與原來保持完全一致。

    圖 2. 動(dòng)態(tài)生成的代理類的繼承圖

    模板

    通過對(duì) Java 動(dòng)態(tài)代理機(jī)制的推演,我們已經(jīng)獲得了一個(gè)通用的方法模板。可以預(yù)期的是,通過模板來定制和引導(dǎo)代理類的代碼生成,是比較可行的方法。我們將主要使用兩個(gè)模板:類模板和方法模板。

    清單 1. 類模板
    package &Package; final public class &Name &Extends &Implements {private java.lang.reflect.InvocationHandler handler = null;&Constructors&Methods }

    類模板定制了代理類的代碼框架。其中帶“&”前綴的標(biāo)簽位被用來引導(dǎo)相應(yīng)的代碼替換。在此預(yù)留了包(&Package)、類名(&ClassName)、類繼承(&Extends)、接口實(shí)現(xiàn)(&Implements)、構(gòu)造函數(shù)集(&Constructors)及方法集(&Methods)的標(biāo)簽位。類模板還同時(shí)聲明了一個(gè)私有型的調(diào)用處理器對(duì)象作為類成員。

    清單 2. 方法模板
    &Modifiers &ReturnType &MethodName(&Parameters) &Throwables {java.lang.reflect.Method method = null;try {method = &Class.getMethod( \"& MethodName\", &ParameterTypes );}catch(Exception e){}Object r = null;try{r = handler.invoke( this, method, &ParameterValues );}&Exceptions&Return }

    方法模板定制了代理類方法集合中各個(gè)方法的代碼框架,同樣的帶“&”前綴的標(biāo)簽位被用來引導(dǎo)相應(yīng)的代碼替換。在此預(yù)留了修飾符(&Modifiers)、返回類型(&ReturnType)、方法名(&MethodName)、參數(shù)列表(Parameters)、異常列表(&Throwables)、方法的聲明類(&Class)、參數(shù)類型列表(&ParameterTypes)、調(diào)用處理器的參數(shù)值列表(&ParameterValues),異常處理(&Exceptions)及返回值(&Return)的標(biāo)簽位。

    代碼生成

    有了類模板和方法模板,代碼生成過程就變得有章可依。基本過程可分為三步:1)生成代理類的方法集合;2)生成代理類的構(gòu)造函數(shù);3)最后生成整個(gè)代理類。

    生成代理類的方法集

    第一步,通過反射獲得被代理類的所有 public 或 protected 且非 static 的 Method 對(duì)象列表,這些方法將被涵蓋的原因是它們是可以被其他類所訪問的。

    第二步,遍歷 Method 對(duì)象列表,對(duì)每個(gè) Method 對(duì)象,進(jìn)行相應(yīng)的代碼生成工作。

    清單 3. 對(duì)標(biāo)簽位進(jìn)行代碼替換生成方法代碼
    String declTemplate = "&Modifiers &ReturnType &MethodName(&Parameters) &Throwables"; String bodyTemplate = "&Declaration &Body"; // 方法聲明 String declare = declTemplate.replaceAll("&Modifiers", getMethodModifiers( method )).replaceAll("&ReturnType", getMethodReturnType( method )).replaceAll("&MethodName", method.getName()).replaceAll("&Parameters", getMethodParameters( method )).replaceAll("&Throwables", getMethodThrowables( method ));// 方法聲明以及實(shí)現(xiàn) String body = bodyTemplate.replaceAll("&Declaration", declare ).replaceAll("&Body", getMethodEntity( method ));

    這里涉及了一些 ProxyEx 類的私有的輔助函數(shù)如 getMethodModifiers 和 getMethodReturnType 等等,它們都是通過反射獲取所需的信息,然后動(dòng)態(tài)地生成各部分代碼。函數(shù) getMethodEntity 是比較重要的輔助函數(shù),它又調(diào)用了其他的輔助函數(shù)來生成代碼并替換標(biāo)簽位。

    清單 4. ProxyEx 的靜態(tài)方法 getMethodEntity()
    private static String getMethodEntity( Method method ) {String template = "\n{"+ "\n java.lang.reflect.Method method = null;"+ "\n try{"+ "\n method = &Class.getMethod( \"&MethodName\", &ParameterTypes );"+ "\n }"+ "\n catch(Exception e){"+ "\n }"+ "\n Object r = null;"+ "\n try{" + "\n r = handler.invoke( this, method, &ParameterValues );"+ "\n }&Exceptions"+ "\n &Return"+ "\n}";String result = template.replaceAll("&MethodName", method.getName() ).replaceAll("&Class", method.getDeclaringClass().getName() + ".class").replaceAll("&ParameterTypes", getMethodParameterTypesHelper(method)).replaceAll("&ParameterValues", getMethodParameterValuesHelper(method) ).replaceAll("&Exceptions", getMethodParameterThrowablesHelper(method)).replaceAll("&Return", getMethodReturnHelper( method ) );return result; }

    當(dāng)為 Class 類型對(duì)象生成該類型對(duì)應(yīng)的字符代碼時(shí),可能涉及數(shù)組類型,反推過程會(huì)需要按遞歸方法生成代碼,這部分工作由 getTypeHelper 方法提供

    清單 5. ProxyEx 的靜態(tài)方法 getTypeHelper()
    private static String getTypeHelper(Class type) {if( type.isArray() ){Class c = type.getComponentType();return getTypeHelper(c) + "[]";}else{return type.getName();} }

    第三步,將所生成的方法保存進(jìn)一個(gè) map 表,該表記錄的是鍵值對(duì)(方法聲明,方法實(shí)現(xiàn))。由于類的多態(tài)性,父類的方法可能被子類所覆蓋,這時(shí)以上通過遍歷所得的方法列表中就會(huì)出現(xiàn)重復(fù)的方法對(duì)象,維護(hù)該表可以很自然地達(dá)到避免方法重復(fù)生成的目的,這就維護(hù)該表的原因所在。

    生成代理類的構(gòu)造函數(shù)

    相信讀者依然清晰記得代理類是通過其構(gòu)造函數(shù)反射生成的,而構(gòu)造時(shí)傳入的唯一參數(shù)就是調(diào)用處理器對(duì)象。為了保持與原代理機(jī)制的一致性,新的代理類的構(gòu)造函數(shù)也同樣只有一個(gè)調(diào)用處理器對(duì)象作為參數(shù)。模板簡單如下

    清單 6. 構(gòu)造函數(shù)模板
    public &Constructor(java.lang.reflect.InvocationHandler handler) { super(&Parameters); this.handler = handler; }

    需要特別提一下的是 super 方法的參數(shù)值列表 &Parameters 的生成,我們借鑒了 Mock 思想,側(cè)重于追求對(duì)象構(gòu)造的成功,而并未過多地努力分析并尋求最準(zhǔn)確最有意義的賦值。對(duì)此,相信讀者會(huì)多少產(chǎn)生一些疑慮,但稍后我們會(huì)提及改進(jìn)的方法,請(qǐng)先繼續(xù)閱讀。

    生成整個(gè)代理類

    通過以上步驟,構(gòu)造函數(shù)和所有需被代理的方法的代碼已經(jīng)生成,接下來就是生成整個(gè)代理類的時(shí)候了。這個(gè)過程也很直觀,通過獲取相關(guān)信息并對(duì)類模板中各個(gè)標(biāo)簽位進(jìn)行替換,便可以輕松的完成整個(gè)代理類的代碼生成。

    被遺忘的角落:類變量

    等等,似乎遺忘了什么?從調(diào)用者的角度出發(fā),我們希望代理類能夠作為被代理類的如實(shí)代表呈現(xiàn)在用戶面前,包括其內(nèi)部狀態(tài),而這些狀態(tài)通常是由類變量所體現(xiàn)出來的,于是就涉及到類變量的代理問題。

    要解決這個(gè)問題,首先需要思考何時(shí)兩者的類變量可能出現(xiàn)不一致?回答了這個(gè)問題,也就找到了解決思路。回顧代理類的構(gòu)造函數(shù),我們以粗糙的方式構(gòu)造了代理類實(shí)例。它們可能一開始就已經(jīng)不一致了。還有每次方法調(diào)用也可能導(dǎo)致被兩者的類變量的不一致。如何解決?直觀的想法是:1)構(gòu)造時(shí)需設(shè)法進(jìn)行同步;2)方法調(diào)用之前和之后也需設(shè)法進(jìn)行同步。這樣,我們就能夠有效避免代理類和被代理類的類變量不一致的問題的出現(xiàn)了。

    但是,如何獲得被代理類的實(shí)例呢?從當(dāng)前的的設(shè)計(jì)中已經(jīng)沒有辦法做到。既然如此,那就繼續(xù)我們的擴(kuò)展之旅。只不過這次擴(kuò)展的對(duì)象是調(diào)用處理器接口,我們將在擴(kuò)展后的接口里加入獲取被代理類對(duì)象的方法,且擴(kuò)展調(diào)用處理器接口將以 static 和 public 的形式被定義在 ProxyEx 類中。

    清單 7. ProxyEx 類內(nèi)的靜態(tài)接口 InvocationHandlerEx
    public static interface InvocationHandlerEx extends InvocationHandler { // 返回指定 stubClass 參數(shù)所對(duì)應(yīng)的被代理類實(shí)體對(duì)象Object getStub(Class stubClass); }

    新的調(diào)用處理器接口具備了獲取被代理類對(duì)象的能力,從而為實(shí)現(xiàn)類變量的同步打開了通道。接下來還需要的就是執(zhí)行類變量同步的 sync 方法,每個(gè)動(dòng)態(tài)生成的代理類中都會(huì)被悄悄地加入這個(gè)私有方法以供調(diào)用。每次方法被分派轉(zhuǎn)發(fā)到調(diào)用處理器執(zhí)行之前和之后,sync 方法都會(huì)被調(diào)用,從而保證類變量的雙向?qū)崟r(shí)更新。相應(yīng)的,方法模板也需要更新以支持該新特性。

    清單 8. 更新后的方法模板(部分)
    Object r = null; try{// 代理類到被代理類方向的變量同步sync(&Class, true);r = handler.invoke( this, method, &ParameterValues );// 被代理類到代理類方向的變量同步sync(&Class, false); }&Exceptions&Return

    sync 方法還會(huì)在構(gòu)造函數(shù)尾部被調(diào)用,從而將被代理類對(duì)象的變量信息同步到代理類對(duì)象,實(shí)現(xiàn)類似于拷貝構(gòu)造的等價(jià)效果。相應(yīng)的,構(gòu)造函數(shù)模板也需要更新以支持該新特性。

    清單 9. 更新后的構(gòu)造函數(shù)模板
    public &Name(java.lang.reflect.InvocationHandler handler) {super(&Parameters);this.handler = handler;// 被代理類到代理類方向的變量同步sync(null, false); }

    接下來介紹 sync 方法的實(shí)現(xiàn),其思想就是首先獲取被代理類的所有 Field 對(duì)象的列表,并通過擴(kuò)展的調(diào)用處理器獲得方法的聲明類說對(duì)應(yīng)的 stub 對(duì)象,然后遍歷 Field 對(duì)象列表并對(duì)各個(gè)變量進(jìn)行拷貝同步。

    清單 10. 聲明在動(dòng)態(tài)生成的代理類內(nèi)部的 snyc 函數(shù)
    private synchronized void sync(java.lang.Class clazz, boolean toStub) {// 判斷是否為擴(kuò)展調(diào)用處理器if( handler instanceof InvocationHandlerEx ){java.lang.Class superClass = this.getClass().getSuperclass();java.lang.Class stubClass = ( clazz != null ? clazz : superClass );// 通過擴(kuò)展調(diào)用處理器獲得stub對(duì)象Object stub = ((InvocationHandlerEx)handler).getStub(stubClass);if( stub != null ){// 獲得所有需同步的類成員列表,遍歷并同步j(luò)ava.lang.reflect.Field[] fields = getFields(superClass);for(int i=0; fields!=null&&i<fields.length; i++){try{fields[i].setAccessible(true);// 執(zhí)行代理類和被代理類的變量同步if(toStub){fields[i].set(stub, fields[i].get(this));}else{fields[i].set(this, fields[i].get(stub));}}catch(Throwable e){}}}} }

    這里涉及到一個(gè)用于獲取類的所有 Field 對(duì)象列表的靜態(tài)輔助方法 getFields。為了提高頻繁查詢時(shí)的性能,配合該靜態(tài)方法的是一個(gè)靜態(tài)的 fieldsMap 對(duì)象,用于記錄已查詢過的類其所包含的 Field 對(duì)象列表,使得再次查詢時(shí)能迅速返回其對(duì)應(yīng)列表。相應(yīng)的,類模板也需進(jìn)行更新。

    清單 11. 增加了靜態(tài) fieldsMap 變量后的類模板
    package &Package; final public class &Name &Extends &Implements {private static java.util.HashMap fieldsMap = new java.util.HashMap();private java.lang.reflect.InvocationHandler handler = null;&Constructors&Methods }
    清單 12. 聲明在動(dòng)態(tài)生成的代理類內(nèi)部的靜態(tài)方法 getFields
    private static java.lang.reflect.Field[] getFields(java.lang.Class c) {if( fieldsMap.containsKey(c) ){return (java.lang.reflect.Field[])fieldsMap.get(c);}java.lang.reflect.Field[] fields = null;if( c == java.lang.Object.class ){fields = c.getDeclaredFields();}else{java.lang.reflect.Field[] fields0 = getFields(c.getSuperclass());java.lang.reflect.Field[] fields1 = c.getDeclaredFields();fields = new java.lang.reflect.Field[fields0.length + fields1.length];System.arraycopy(fields0, 0, fields, 0, fields0.length);System.arraycopy(fields1, 0, fields, fields0.length, fields1.length);}fieldsMap.put(c, fields);return fields; }

    動(dòng)態(tài)編譯及裝載

    代碼生成以后,需要經(jīng)過編譯生成 JVM 所能識(shí)別的字節(jié)碼,而字節(jié)碼還需要通過類裝載器載入 JVM 才能最終被真正使用,接下來我們將闡述如何動(dòng)態(tài)編譯及裝載。

    首先是動(dòng)態(tài)編譯。這部分由 ProxyEx 類的 getProxyClassCodeSource 函數(shù)完成。該函數(shù)分三步進(jìn)行:第一步保存源代碼到 .java 文件;第二步編譯該 .java 文件;第三步從輸出的 .class 文件讀取字節(jié)碼。

    清單 13. ProxyEx 的靜態(tài)方法 getProxyClassCodeSource
    private static byte[] getProxyClassCodeSource( String pkg, String className, String declare ) throws Exception {// 將類的源代碼保存進(jìn)一個(gè)名為類名加“.java”的本地文件File source = new File(className + ".java");FileOutputStream fos = new FileOutputStream( source );fos.write( declare.getBytes() );fos.close();// 調(diào)用com.sun.tools.javac.Main類的靜態(tài)方法compile進(jìn)行動(dòng)態(tài)編譯int status = com.sun.tools.javac.Main.compile( new String[] { "-d", ".", source.getName() } );if( status != 0 ){source.delete();throw new Exception("Compiler exit on " + status);}// 編譯得到的字節(jié)碼將被輸出到與包結(jié)構(gòu)相同的一個(gè)本地目錄,文件名為類名加”.class”String output = ".";int curIndex = -1;int lastIndex = 0;while( (curIndex=pkg.indexOf('.', lastIndex)) != -1 ){output = output + File.separator + pkg.substring( lastIndex, curIndex );lastIndex = curIndex + 1;}output = output + File.separator + pkg.substring( lastIndex );output = output + File.separator + className + ".class";// 從輸出文件中讀取字節(jié)碼,并存入字節(jié)數(shù)組File target = new File(output);FileInputStream f = new FileInputStream( target );byte[] codeSource = new byte[(int)target.length()];f.read( codeSource );f.close();// 刪除臨時(shí)文件source.delete();target.delete();return codeSource; }

    得到代理類的字節(jié)碼,接下來就可以動(dòng)態(tài)裝載該類了。這部分由 ProxyEx 類的 defineClassHelper 函數(shù)完成。該函數(shù)分兩步進(jìn)行:第一步通過反射獲取父類 Proxy 的靜態(tài)私有方法 defineClass0;第二步傳入字節(jié)碼數(shù)組及其他相關(guān)信息并反射調(diào)用該方法以完成類的動(dòng)態(tài)裝載。

    清單 14. ProxyEx 的靜態(tài)方法 defineClassHelper
    private static Class defineClassHelper( String pkg, String cName, byte[] codeSource ) throws Exception {Method defineClass = Proxy.class.getDeclaredMethod( "defineClass0", new Class[] { ClassLoader.class, String.class,byte[].class,int.class,int.class } );defineClass.setAccessible(true);return (Class)defineClass.invoke( Proxy.class, new Object[] { ProxyEx.class.getClassLoader(), pkg.length()==0 ? cName : pkg+"."+cName,codeSource,new Integer(0),new Integer(codeSource.length) } ); }

    性能改進(jìn)

    原動(dòng)態(tài)代理機(jī)制中對(duì)接口數(shù)組有一些有趣的特點(diǎn),其中之一就是接口的順序差異會(huì)在一定程度上導(dǎo)致生成新的代理類,即使其實(shí)并無必要。其中的原因就是因?yàn)榫彺姹硎且越涌诿Q列表作為關(guān)鍵字,所以不同的順序就意味著不同的關(guān)鍵字,如果對(duì)應(yīng)的關(guān)鍵字不存在,就會(huì)生成新但是作用重復(fù)的代理類。在 ProxyEx 類中,我們通過主動(dòng)排序避免了類似的問題,提高動(dòng)態(tài)生成代理類的效率。而且,如果發(fā)現(xiàn)數(shù)組中都是接口類型,則直接調(diào)用父類 Proxy 的靜態(tài)方法 getProxyClass 生成代理類,否則才通過擴(kuò)展動(dòng)態(tài)代理機(jī)制生成代理類,這樣也一定程度上改進(jìn)了性能。

    兼容性問題

    接下來需要考慮的是與原代理機(jī)制的兼容性問題。曾記否,Proxy 中還有兩個(gè)靜態(tài)方法:isProxyClass 和 getInvocationHandler,分別被用于判斷 Class 對(duì)象是否是動(dòng)態(tài)代理類和從 Object 對(duì)象獲取對(duì)應(yīng)的調(diào)用處理器(如果可能的話)。

    清單 15. Proxy 的靜態(tài)方法 isProxyClass 和 getInvocationHandler
    static boolean isProxyClass(Class cl) static InvocationHandler getInvocationHandler(Object proxy)

    現(xiàn)在的兼容性問題,主要涉及到 ProxyEx 類與父類 Proxy 在關(guān)于動(dòng)態(tài)生成的代理類的信息方面所面臨的如何保持同步的問題。曾介紹過,在 Proxy 類中有個(gè)私有的 Map 對(duì)象 proxyClasses 專門負(fù)責(zé)保存所有動(dòng)態(tài)生成的代理類類型。Proxy 類的靜態(tài)函數(shù) isProxyClass 就是通過查詢?cè)摫硪源_定某 Class 對(duì)象是否為動(dòng)態(tài)代理類,我們需要做的就是把由 ProxyEx 生成的代理類類型也保存入該表。這部分工作由 ProxyEx 類的靜態(tài)方法 addProxyClass 輔助完成。

    清單 16. ProxyEx 的靜態(tài)方法 addProxyClass
    private static void addProxyClass( Class proxy ) throws IllegalArgumentException { try { // 通過反射獲取父類的私有 proxyClasses 變量并更新Field proxyClasses = Proxy.class.getDeclaredField("proxyClasses"); proxyClasses.setAccessible(true); ((Map)proxyClasses.get(Proxy.class)).put( proxy, null ); } catch(Exception e) { throw new IllegalArgumentException(e.toString()); } }

    相對(duì)而言,原來 Proxy 類的靜態(tài)方法 getInvocationHandler 實(shí)現(xiàn)相當(dāng)簡單,先判斷是否為代理類,若是則直接類型轉(zhuǎn)換到 Proxy 并返回其調(diào)用處理器成員,而擴(kuò)展后的代理類并不非從 Proxy 類繼承,所以在獲取調(diào)用處理器對(duì)象的方法上需要一些調(diào)整。這部分由 ProxyEx 類的同名靜態(tài)方法 getInvocationHandler 完成。

    清單 17. ProxyEx 的靜態(tài)方法 getInvocationHandler
    public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException {// 如果Proxy實(shí)例,直接調(diào)父類的方法if( proxy instanceof Proxy ) {return Proxy.getInvocationHandler( proxy );}// 如果不是代理類,拋異常if( !Proxy.isProxyClass( proxy.getClass() )){throw new IllegalArgumentException("Not a proxy instance");}try {// 通過反射獲取擴(kuò)展代理類的調(diào)用處理器對(duì)象Field invoker = proxy.getClass().getDeclaredField("handler");invoker.setAccessible(true);return (InvocationHandler)invoker.get(proxy);}catch(Exception e){throw new IllegalArgumentException("Suspect not a proxy instance");} }

    坦言:也有局限

    受限于 Java 的類繼承機(jī)制,擴(kuò)展的動(dòng)態(tài)代理機(jī)制也有其局限,它不能支持:

  • 聲明為 final 的類;
  • 聲明為 final 的函數(shù);
  • 構(gòu)造函數(shù)均為 private 類型的類;
  • 實(shí)例演示

    闡述了這么多,相信讀者一定很想看一下擴(kuò)展動(dòng)態(tài)代理機(jī)制是如何工作的。本文最后將以 2010 世博門票售票代理為模型進(jìn)行演示。

    首先,我們定義了一個(gè)售票員抽象類 TicketSeller。

    清單 18. TicketSeller
    public abstract class TicketSeller {protected String theme;protected TicketSeller(String theme){this.theme = theme;}public String getTicketTheme(){return this.theme;}public void setTicketTheme(String theme){this.theme = theme;}public abstract int getTicketPrice();public abstract int buy(int ticketNumber, int money) throws Exception; }

    其次,我們會(huì)實(shí)現(xiàn)一個(gè) 2010 世博門票售票代理類 Expo2010TicketSeller。

    清單 19. Expo2010TicketSeller
    public class Expo2010TicketSeller extends TicketSeller {protected int price;protected int numTicketForSale;public Expo2010TicketSeller(){super("World Expo 2010");this.price = 180;this.numTicketForSale = 200;}public int getTicketPrice(){return price;}public int buy(int ticketNumber, int money) throws Exception{if( ticketNumber > numTicketForSale ){throw new Exception("There is no enough ticket available for sale, only " + numTicketForSale + " ticket(s) left");}int charge = money - ticketNumber * price;if( charge < 0 ){throw new Exception("Money is not enough. Still needs " + (-charge) + " RMB.");}numTicketForSale -= ticketNumber;return charge;} }

    接著,我們將通過購票者類 TicketBuyer 來模擬購票以演示擴(kuò)展動(dòng)態(tài)代理機(jī)制。

    清單 20. TicketBuyer
    public class TicketBuyer {public static void main(String[] args) {// 創(chuàng)建真正的TickerSeller對(duì)象,作為stub實(shí)體final TicketSeller stub = new Expo2010TicketSeller();// 創(chuàng)建擴(kuò)展調(diào)用處理器對(duì)象InvocationHandler handler = new InvocationHandlerEx(){public Object getStub(Class stubClass) {// 僅對(duì)可接受的Class類型返回stub實(shí)體if( stubClass.isAssignableFrom(stub.getClass()) ){return stub;}return null;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object o;try{System.out.println(" >>> Enter method: " + method.getName() );o = method.invoke(stub, args);}catch(InvocationTargetException e){throw e.getCause();}finally{System.out.println(" <<< Exit method: " + method.getName() );}return o;}};// 通過ProxyEx構(gòu)造動(dòng)態(tài)代理TicketSeller seller = (TicketSeller)ProxyEx.newProxyInstance(TicketBuyer.class.getClassLoader(), new Class[] {TicketSeller.class}, handler);// 顯示代理類的類型System.out.println("Ticket Seller Class: " + seller.getClass() + "\n");// 直接訪問theme變量,驗(yàn)證代理類變量在對(duì)象構(gòu)造時(shí)同步的有效性System.out.println("Ticket Theme: " + seller.theme + "\n");// 函數(shù)訪問price信息System.out.println("Query Ticket Price...");System.out.println("Ticket Price: " + seller.getTicketPrice() + " RMB\n");// 模擬票務(wù)交易buyTicket(seller, 1, 200);buyTicket(seller, 1, 160);buyTicket(seller, 250, 30000);// 直接更新theme變量System.out.println("Updating Ticket Theme...\n");seller.theme = "World Expo 2010 in Shanghai";// 函數(shù)訪問theme信息,驗(yàn)證擴(kuò)展動(dòng)態(tài)代理機(jī)制對(duì)變量同步的有效性System.out.println("Query Updated Ticket Theme...");System.out.println("Updated Ticket Theme: " + seller.getTicketTheme() + "\n"); }// 購票函數(shù)protected static void buyTicket(TicketSeller seller, int ticketNumber, int money){try {System.out.println("Transaction: Order " + ticketNumber + " ticket(s) with " + money + " RMB");int charge = seller.buy(ticketNumber, money);System.out.println("Transaction: Succeed - Charge is " + charge + " RMB\n");} catch (Exception e) {System.out.println("Transaction: Fail - " + e.getMessage() + "\n");} } }

    最后,見演示程序的執(zhí)行結(jié)果。

    清單 21. 執(zhí)行輸出
    Ticket Seller Class: class com.demo.proxy.test.TicketSellerProxy0Ticket Theme: World Expo 2010Query Ticket Price...>>> Enter method: getTicketPrice<<< Exit method: getTicketPrice Ticket Price: 180 RMBTransaction: Order 1 ticket(s) with 200 RMB>>> Enter method: buy<<< Exit method: buy Transaction: Succeed - Charge is 20 RMBTransaction: Order 1 ticket(s) with 160 RMB>>> Enter method: buy<<< Exit method: buy Transaction: Fail - Money is not enough. Still needs 20 RMB.Transaction: Order 250 ticket(s) with 30000 RMB>>> Enter method: buy<<< Exit method: buy Transaction: Fail - There is no enough ticket available for sale, only 199 ticket(s) leftUpdating Ticket Theme...Query Updated Ticket Theme...>>> Enter method: getTicketTheme<<< Exit method: getTicketTheme Updated Ticket Theme: World Expo 2010 in Shanghai

    ?

    轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/3474867.html

    總結(jié)

    以上是生活随笔為你收集整理的Java 动态代理机制分析及扩展--转的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    一区二区三区四区五区在线 | 欧美激情另类 | 成人av一区二区在线观看 | 亚洲 欧美 另类人妖 | 成人片在线播放 | 欧美在线一 | 91爱爱视频| 亚洲欧美乱综合图片区小说区 | 97精品国产97久久久久久春色 | 天天操天天操天天操天天操 | 久久久久久综合 | av专区在线 | www视频在线观看 | 中文字幕第一页av | 国产视频97 | 日日爽天天爽 | 欧美日韩精品在线观看 | 国产特级毛片aaaaaa | 手机在线日韩视频 | 在线观看日韩中文字幕 | 国产毛片在线 | 精品久久网 | 99日精品| 亚洲免费一级 | 免费a视频| 毛片激情永久免费 | 久久av网| 国产黄色片久久久 | 欧美精品久久久久久久免费 | 中文字幕人成一区 | 玖玖视频 | 日韩中文字幕免费 | 91免费看黄色 | 国产不卡网站 | 精品99久久久久久 | 免费中文字幕视频 | 免费看v片 | 黄色成人影院 | 97福利视频| 狠狠色伊人亚洲综合网站野外 | 欧美孕妇与黑人孕交 | 亚洲精品国产成人 | 午夜 免费 | 欧美亚洲国产一卡 | 伊人五月在线 | 日本激情视频中文字幕 | 日韩精品免费在线 | 欧美日韩后 | 日韩欧美一区二区在线 | 97超视频在线观看 | 久久久久免费观看 | 国产1区在线 | 99视频国产精品 | 欧美日韩国产精品一区二区亚洲 | 国产资源免费 | 久久国产亚洲视频 | 久久精品久久综合 | 天天综合网天天综合色 | 国产精品99精品 | 二区三区毛片 | 丁香婷婷色月天 | 午夜视频久久久 | 99久久久国产精品 | 日日干网 | 婷婷资源站| 91在线蜜桃臀 | 免费视频在线观看网站 | 97网在线观看 | 高潮久久久 | 亚洲美女视频网 | 顶级欧美色妇4khd | 亚州成人av在线 | 天天爽天天摸 | 91激情在线视频 | 亚洲精品国产品国语在线 | 97在线观看免费观看高清 | 免费高清看电视网站 | 国产亚洲精品中文字幕 | 9在线观看免费高清完整 | 国产亚洲日本 | 久久男人中文字幕资源站 | 在线免费亚洲 | 中文字幕在线免费看 | 亚洲激情| 久久精品国产成人精品 | 国产精品一区二区三区免费看 | 免费看网站在线 | 国产成人久久精品亚洲 | 99精品免费 | 97视频在线免费播放 | 国产小视频精品 | 日韩在线短视频 | 日本三级中文字幕在线观看 | 操操日日| 狠狠色婷婷丁香六月 | 天天操天天爱天天爽 | 久久九九影院 | 一区二区三区四区精品视频 | 婷婷久久久久 | 日韩免费一级电影 | 久久精品视频在线 | 日韩com | 国产一区黄色 | 国产一区自拍视频 | 精品视频成人 | 国产精品都在这里 | 在线视频18在线视频4k | 欧美成年人在线视频 | 十八岁免进欧美 | 91av观看 | 国产剧情久久 | 欧美日韩精品区 | 97国产精品一区二区 | 九九九视频精品 | 久久公开视频 | 亚洲激情综合 | 伊人永久在线 | 日韩中文免费视频 | 99婷婷| 精品久久久久久亚洲综合网站 | 97超碰伊人| 欧美日韩国产精品久久 | 日韩国产欧美在线视频 | 欧美韩国日本在线观看 | 91一区啪爱嗯打偷拍欧美 | 日本巨乳在线 | 成年人网站免费在线观看 | 免费观看视频黄 | 日韩二区在线 | 9999精品视频| 天天射天天拍 | 欧美精品免费在线观看 | 久久久久国产精品厨房 | 日韩高清在线一区二区三区 | 人人玩人人添人人澡97 | 国产精品久久久久久久久久了 | 色综合久久网 | 特级片免费看 | av官网| 国产人成看黄久久久久久久久 | 日日夜夜精品视频天天综合网 | 91黄视频在线 | 麻豆av一区二区三区在线观看 | 亚洲欧美成人网 | 久久国产品 | 日本久久久久久科技有限公司 | 免费观看国产视频 | 国产免费观看视频 | 久久久蜜桃一区二区 | 日韩免费高清 | 久久黄色精品视频 | 久久1区 | 国产精品嫩草影院99网站 | 1区2区视频| 一级α片免费看 | 久久精品国产成人 | 最新av在线播放 | 成人日韩av | 国产精品久久9 | 在线观看黄色大片 | www.天天色 | 欧美一区二区三区四区夜夜大片 | 97人人艹| a成人v在线 | 在线播放 一区 | www.天天干| 一级免费看| 日韩电影黄色 | 日日夜夜狠狠操 | 国产高清在线一区 | 国产精品第二十页 | 韩国在线视频一区 | 欧美精品久久久久性色 | 国产97在线视频 | 麻豆免费精品视频 | 五月婷婷中文网 | 亚洲免费在线看 | 国产美女免费 | 国产 视频 久久 | 香蕉网在线播放 | 国产亚洲成人网 | 国产色视频网站 | 欧美性生活久久 | 欧美一级黄色片 | 99久久精品国产观看 | 国产视频 亚洲视频 | 不卡电影一区二区三区 | 国产探花视频在线播放 | 亚洲欧洲精品一区二区精品久久久 | 中文字幕一区二区三区乱码不卡 | 99超碰在线观看 | 色999五月色| 免费在线视频一区二区 | 爱爱av网站| 国产91免费看 | 亚洲午夜精品一区二区三区电影院 | 日韩中文在线电影 | 国产明星视频三级a三级点| 四虎成人精品永久免费av | 国产色拍拍拍拍在线精品 | 免费一级日韩欧美性大片 | 国产夫妻av在线 | 九九热在线精品视频 | 粉嫩高清一区二区三区 | 色噜噜在线观看视频 | 国产婷婷色| 在线免费观看视频你懂的 | 精品一区久久 | 国产高清在线看 | 在线视频中文字幕一区 | 五月在线| 欧美精品久久久久久久久久久 | 日韩欧美电影在线 | 日韩电影在线看 | 天堂av在线免费 | 久久久蜜桃一区二区 | 天天综合网天天 | 玖玖综合网| 99中文字幕 | 精品视频久久久 | 日韩有码在线观看视频 | 欧美国产精品久久久久久免费 | 久热电影| 国产视频一区二区在线 | 97网在线观看 | 天天射网站| 亚洲 欧美变态 另类 综合 | 久久av中文字幕片 | 日韩大片在线免费观看 | 91九色视频在线观看 | 国产一区私人高清影院 | 亚洲人成网站精品片在线观看 | 免费又黄又爽的视频 | 国产一区二区三区久久久 | 2023av| 91黄视频在线观看 | 色吧av色av | 精品国产亚洲一区二区麻豆 | 99夜色 | 一区二区三区在线免费播放 | www.av在线.com| 色婷婷导航 | 日韩美一区二区三区 | 久久国产美女视频 | 亚洲 综合 国产 精品 | 91成年视频 | 亚洲精品综合一区二区 | 视频一区亚洲 | 亚洲成人av片 | 久久久精品二区 | 免费成视频 | 国产免费人成xvideos视频 | 亚洲国产免费av | 在线观看中文 | 五月婷婷综合在线视频 | 日批视频在线播放 | av一级在线观看 | 成人亚洲综合 | 成人av网站在线 | 欧美日韩国产一区二区三区 | 国产精品一区二区你懂的 | 久久综合免费视频影院 | 91av在线不卡 | 欧美日韩国内在线 | 亚洲视频456| 99视| 亚洲午夜久久久久久久久久久 | 另类五月激情 | 这里只有精品视频在线观看 | 我要色综合天天 | 激情婷婷久久 | 天天伊人狠狠 | 免费成人在线网站 | 久久中文字幕在线视频 | 99久久电影| 国内久久久 | 国产精品毛片一区二区三区 | 黄色在线网站噜噜噜 | 超碰人人在线观看 | 国产中文字幕一区二区 | 色中文字幕在线观看 | 国产精品久久久久aaaa | 久久精品免费看 | 国产伦理一区二区三区 | 免费高清看电视网站 | 久久人人爽人人爽人人片av软件 | 免费在线观看一区二区三区 | 精品在线视频一区 | 九草在线观看 | 99久久精品国产观看 | 丁香亚洲 | 一本到在线 | 96亚洲精品久久久蜜桃 | 欧美精品久久久久久久免费 | 欧洲一区精品 | 久久99精品久久久久蜜臀 | 久草视频在线免费 | 亚洲精品www. | 国产精品久久久久久久久久免费看 | 九九九热 | 成人av电影免费在线播放 | 欧美日韩国产欧美 | 久久久免费看视频 | 国产精品一区二区在线播放 | 在线观看免费av网站 | 免费男女羞羞的视频网站中文字幕 | 操操操人人人 | 欧美日韩视频在线观看一区二区 | 深爱婷婷网 | 国产日韩精品欧美 | 久久国产一区二区 | 久久久久成人精品 | 午夜久久网 | zzijzzij亚洲日本少妇熟睡 | 91在线看黄| 精品在线观看免费 | 日韩高清免费在线 | 91免费在线 | 欧美久久成人 | 日本黄色免费电影网站 | 日韩欧美国产成人 | 国产在线精品视频 | 欧美少妇xxxxxx | 91精品成人 | 99在线观看免费视频精品观看 | 欧美一级片免费观看 | 免费大片av | 欧美激情第28页 | 久久国内视频 | 在线观看深夜福利 | 色综合夜色一区 | 色视频国产直接看 | 黄影院| 天天天干天天射天天天操 | 五月综合色婷婷 | 日本中文字幕网站 | aaa日本高清在线播放免费观看 | www.久久久久| 久久a热6| 精品国产乱码久久久久久久 | 精品美女在线视频 | 欧美成年网站 | 少妇超碰在线 | 麻豆视频在线观看免费 | 欧美91精品久久久久国产性生爱 | 天天射天天爽 | 国产美女久久 | 成人午夜网址 | 伊人中文在线 | 97电影手机版| 精品夜夜嗨av一区二区三区 | 四虎在线观看 | 黄色大片av | 手机看片 | 麻豆视频免费在线观看 | 九九在线视频免费观看 | 天天插天天狠 | 超碰在线天天 | 日韩剧 | av不卡中文 | 99免费在线视频观看 | 在线一区观看 | 久久福利综合 | 国产精品手机在线播放 | 婷婷精品国产欧美精品亚洲人人爽 | 国产免费视频在线 | 少妇高潮冒白浆 | 亚洲成av人片在线观看www | 国产综合在线视频 | 胖bbbb搡bbbb擦bbbb | 91亚洲狠狠婷婷综合久久久 | 一区二区三区免费在线播放 | 97成人在线 | 欧美中文字幕第一页 | 月丁香婷婷 | 婷婷亚洲综合 | 国产电影黄色av | 黄色片视频免费 | 国产精品9区| 久久精品久久久久 | 日日干网址 | 午夜精品久久久久久久久久久 | 黄网站app在线观看免费视频 | 伊人干综合 | 17婷婷久久www | 欧美久久九九 | av高清一区 | 四虎在线免费观看 | 夜夜干天天操 | 成人av在线网 | 91精品国自产在线观看欧美 | 91视频免费看 | 人人讲下载 | 国产成人精品日本亚洲999 | 欧美色综合天天久久综合精品 | 天天干天天摸 | 久久电影网站中文字幕 | 亚洲精品久久视频 | 天天综合入口 | 久久久黄色 | av在线播放中文字幕 | 国产精品一区二区三区免费看 | 国产黄a三级三级三级三级三级 | 午夜久久影院 | 中文字幕一区二区三区四区久久 | 成人午夜电影在线播放 | 91av社区| 国产精品久久久久久久毛片 | 在线欧美a | 中文字幕久久网 | 亚洲爱视频| 午夜国产在线观看 | 国产婷婷色 | 中文字幕在线观看视频一区二区三区 | 一级片免费视频 | 在线观看黄色大片 | 日韩精品欧美专区 | 美女网站视频免费黄 | 成人91在线| 欧美一级黄大片 | www.黄色片.com | 成人免费视频在线观看 | 中文字幕免费 | 欧洲一区二区三区精品 | 欧美日韩视频在线一区 | 啪嗒啪嗒免费观看完整版 | 久久免费视频2 | 日韩网页 | 国产一级免费视频 | 色香蕉在线视频 | 91黄色影视 | 精品网站999www | 91一区二区在线 | 欧美精品中文字幕亚洲专区 | 成人黄色小说视频 | 欧美激情视频三区 | 在线一二区 | av先锋影音少妇 | 精品麻豆入口免费 | 99草视频| 高清av免费看 | 最新婷婷色 | 久久精品亚洲 | 天天做天天爱天天综合网 | 午夜影院先 | 日韩在线播放欧美字幕 | 成人午夜在线电影 | 亚洲无吗av | 91精品国产91久久久久福利 | 91av短视频 | 人人爽人人爽人人片 | 成人综合日日夜夜 | www夜夜操com | 国产成人一区二区三区久久精品 | 亚洲专区在线播放 | 四虎影视成人永久免费观看视频 | 黄色一级片视频 | 欧美精品久久久久久久久久久 | 草莓视频在线观看免费观看 | 18网站在线观看 | 精品国产一区二区三区不卡 | 成人免费视频网址 | 在线看一级片 | 国产精品久久久久久久久久久久 | 国产高清在线观看av | 日日爱av | 色999五月色| 婷婷狠狠操 | bbw av| 欧美一级网站 | 久久久久福利视频 | 亚洲欧美视频一区二区三区 | 亚洲一区二区精品视频 | av在线电影网站 | 在线激情网 | 嫩草av在线 | 亚洲精品国产品国语在线 | 久久这里只有精品首页 | 国产精品一区二区62 | 免费网站色 | 91麻豆精品国产91久久久使用方法 | 久久久国产精品人人片99精片欧美一 | 久久99精品久久久久久 | 97超碰国产精品 | 日韩v欧美v日本v亚洲v国产v | 麻豆91网站 | 亚洲黄色区 | 婷婷六月色 | 国产亚洲精品久久久久秋 | 天堂网中文在线 | 精品一二三四在线 | 国产成人三级在线观看 | 丰满少妇久久久 | 最新中文字幕在线播放 | 免费91麻豆精品国产自产在线观看 | 97视频精品 | 日日夜日日干 | 超碰在线公开 | 91看片网址 | 狠狠狠狠狠狠操 | 91在线视频精品 | 国产手机视频在线播放 | 色婷婷丁香 | 五月亚洲婷婷 | 日韩av在线一区二区 | 手机av在线网站 | 国产一级特黄电影 | 久久久网址 | 国产精品一区二区久久久久 | 最新av在线免费观看 | 日批视频| 国产婷婷精品av在线 | 亚洲国产欧洲综合997久久, | 免费看的视频 | 日韩在线观看视频一区二区三区 | 日本丶国产丶欧美色综合 | 久久久久国产精品免费免费搜索 | 亚洲一区欧美精品 | 国产区精品在线观看 | 激情伊人五月天久久综合 | 久久精品久久99精品久久 | 日韩亚洲在线观看 | 黄色在线免费观看网址 | 人人看97| 超碰97在线看 | 日韩国产欧美在线视频 | 91精品久久久久久久久 | 亚洲久草视频 | 激情自拍av| 国产中文字幕视频在线观看 | 久久久久国产精品厨房 | 999精品视频 | 热久精品 | www.99在线观看| 一级免费观看 | av资源中文字幕 | 狠狠干五月天 | 97av视频在线观看 | 激情在线网站 | 天天综合狠狠精品 | 日日弄天天弄美女bbbb | 超碰97国产 | 91成人网在线| 福利一区在线视频 | 久久久国产99久久国产一 | 成人av教育 | 97**国产露脸精品国产 | ,午夜性刺激免费看视频 | 成人午夜电影在线 | 欧美一级久久久久 | 国产高清在线 | 综合伊人av | 天天插伊人 | 五月婷丁香 | 玖玖精品视频 | 91视频传媒 | 久久国产精品久久久 | 中文字幕乱码视频 | av在线精品 | 国产欧美在线一区 | 久久99九九99精品 | 麻豆你懂的 | 91精品欧美一区二区三区 | 91av成人 | 日韩网站视频 | 久久久高清免费视频 | 日韩激情中文字幕 | 91精品一区国产高清在线gif | 999久久国精品免费观看网站 | 国产亚洲精品久久网站 | 激情五月婷婷综合网 | 综合久久久久久 | 日韩二区三区 | 精品视频不卡 | 免费在线观看日韩视频 | 国产精品精品久久久久久 | 亚洲激情一区二区三区 | 精品国产欧美一区二区三区不卡 | 欧美-第1页-屁屁影院 | 亚洲国产三级 | 91热视频在线观看 | 亚洲网久久 | 黄色小说在线免费观看 | 伊人va | 亚洲精品裸体 | 免费在线观看日韩欧美 | 尤物97国产精品久久精品国产 | 91成年视频 | 99精品国产高清在线观看 | 正在播放日韩 | 狠狠狠狠狠狠操 | 亚一亚二国产专区 | 一级免费黄色 | 国产黄色片久久 | 99精品视频精品精品视频 | 香蕉影视在线观看 | 国产乱码精品一区二区蜜臀 | 国产精品久久网 | v片在线看 | 国产糖心vlog在线观看 | 久久综合狠狠综合久久狠狠色综合 | 日本久久久亚洲精品 | www.在线观看视频 | 国产精品美女久久久久久2018 | 国内精品久久久久久久久久清纯 | 婷婷精品国产欧美精品亚洲人人爽 | 日韩在线在线 | 精品999久久久 | 在线韩国电影免费观影完整版 | 中文字幕免费高清av | 亚洲91网站 | 亚洲欧洲精品一区二区精品久久久 | 在线成人免费av | 欧美精品久久99 | 婷婷亚洲综合 | 182午夜在线观看 | 久久久久亚洲国产精品 | 国产一区二区三区免费在线 | 久久怡红院 | 久久亚洲综合国产精品99麻豆的功能介绍 | 日本精品一区二区三区在线观看 | 日本午夜免费福利视频 | 人人爽人人爽人人爽 | 国产成人久久av免费高清密臂 | 91新人在线观看 | 国产精品手机在线观看 | 日韩黄色在线观看 | 精品毛片一区二区免费看 | 国产一区国产精品 | 精品毛片一区二区免费看 | 天堂在线一区二区 | 欧美日韩成人一区 | 中国一级片免费看 | 国产精品久久精品国产 | 国产精品久久久久久久电影 | 成人动态视频 | 91资源在线视频 | 涩涩网站在线观看 | 成 人 黄 色视频免费播放 | 美女网站一区 | 97操碰| 亚洲精品美女久久久久 | 亚洲成av| 亚洲播放一区 | 国产高清视频免费 | 国产在线精品二区 | 美女视频黄的免费的 | 91麻豆精品国产91久久久更新时间 | 久久99精品热在线观看 | 夜夜干天天操 | 国产传媒一区在线 | 欧美精品一区二区免费 | 亚洲婷婷在线视频 | 精品久久久久国产免费第一页 | 超碰在线观看99 | 日韩在线精品视频 | 国产理伦在线 | 91女神的呻吟细腰翘臀美女 | 亚洲 在线 | 五月天丁香视频 | 日韩av电影免费在线观看 | 精品免费久久 | 91在线视频观看 | 欧美日韩国产在线观看 | 欧美一级小视频 | 极品嫩模被强到高潮呻吟91 | 欧美一区二区在线 | 午夜免费在线观看 | 人人揉人人揉人人揉人人揉97 | 91九色视频在线 | 91插插影库 | 欧美一级片免费播放 | 午夜精品久久久久久久久久久 | 麻豆一级视频 | 久久在线电影 | 九九热久久免费视频 | 日韩在线在线 | 日韩综合精品 | 天天色天天色 | 99久久精品免费看国产四区 | 人人草在线观看 | 超碰公开97| 国产精品久久一卡二卡 | 国产精品18久久久久久首页狼 | 国产一区观看 | 在线性视频日韩欧美 | 午夜色大片在线观看 | 国产91国语对白在线 | 成人网色| 国产成人久久精品一区二区三区 | 激情av五月婷婷 | 婷婷综合 | 在线免费黄色 | 婷婷激情综合五月天 | 69亚洲精品| 91精选在线 | 精品国产成人av | 超碰在线人人爱 | 久久精品91久久久久久再现 | 99免费观看视频 | 99在线视频网站 | 欧美成人a在线 | 91精品入口 | 国产精华国产精品 | 国产精品久久在线观看 | 婷婷深爱五月 | 免费观看一区二区三区视频 | 久久精品爱爱视频 | 中文字幕一区二区在线观看 | 色婷婷亚洲精品 | 日本精品视频在线播放 | 欧美成人影音 | 中文字幕在线字幕中文 | 免费观看9x视频网站在线观看 | 色网站免费在线看 | 伊人电影天堂 | 国内精品久久久久久久久久久久 | 在线观看视频福利 | 日韩精品久久久免费观看夜色 | 91视频午夜 | 午夜国产一区 | 欧美三级高清 | 亚洲第一伊人 | 人人插人人艹 | 国产资源在线免费观看 | 91一区啪爱嗯打偷拍欧美 | 日韩精品久久久久久久电影竹菊 | 亚洲高清久久久 | 成片免费| 成年人黄色在线观看 | 又色又爽又黄高潮的免费视频 | 日韩精品久久久久 | 国产精品综合av一区二区国产馆 | 超碰在线观看97 | 91在线porny国产在线看 | 免费成人黄色av | 亚洲婷婷网| 狠狠网| 欧美最爽乱淫视频播放 | 亚洲一级片 | 91豆麻精品91久久久久久 | 97色狠狠 | 男女男视频| 九九影视理伦片 | 国产精品久久久av久久久 | 一区二区三区污 | 99精品偷拍视频一区二区三区 | www.国产在线观看 | 免费亚洲精品 | 91看片在线观看 | 黄色大全在线观看 | 91专区在线观看 | 欧美一级免费片 | 91爱爱网址 | 亚洲精品国产精品99久久 | 黄色在线观看免费 | 日韩一区二区在线免费观看 | 亚洲电影图片小说 | 男女精品久久 | 国产亚洲精品久久网站 | 9在线观看免费 | 精品国产视频在线 | 欧美a在线看 | 国产乱老熟视频网88av | 五月婷婷丁香 | 深夜免费福利网站 | 伊人黄色网 | 久久久久久免费网 | 午夜少妇 | 国产高清精 | 国产一区二区三区免费视频 | 国产精品永久免费 | 丁香av在线 | 日韩高清免费在线观看 | 欧美韩国在线 | 久久国产一区二区三区 | 国产精品色婷婷 | 一区二区av | 欧美一区二区在线刺激视频 | 又黄又爽免费视频 | 成人在线免费视频观看 | 国产在线a | 国产精品久久久久久久免费观看 | 婷婷丁香花五月天 | 国产一区二区日本 | 99久久久国产精品免费99 | 免费在线一区二区 | 在线免费观看视频 | 中文字幕免费高清 | 午夜精品久久久久久久99 | 久草在线综合网 | 天天天操天天天干 | 日韩精品视频免费在线观看 | 亚洲国产黄色片 | 天天操天天射天天爽 | 日韩videos| 在线观看 亚洲 | 99在线精品视频观看 | 在线国产不卡 | 超碰免费公开 | 九色91福利 | 国产精品久久久亚洲 | 偷拍区另类综合在线 | 国产精品人成电影在线观看 | zzijzzij亚洲成熟少妇 | 日韩黄色在线观看 | 91在线小视频 | 亚洲日本一区二区在线 | 国产精品嫩草55av | 久草久| 免费合欢视频成人app | www.夜夜操 | 黄色在线观看www | av黄网站 | 欧美精品中文字幕亚洲专区 | 在线看国产日韩 | 91精品国产99久久久久久久 | 九九久久久久久久久激情 | 香蕉视频亚洲 | 草久久久 | 99在线热播精品免费 | 久久国产乱 | 另类老妇性bbwbbw高清 | 欧美精品久久久久久 | 国产高潮久久 | 综合色播 | 国色天香第二季 | 天海翼一区二区三区免费 | 五月婷婷狠狠 | 免费影视大全推荐 | 特级黄色片免费看 | 天天摸日日操 | 久草在线费播放视频 | 2023国产精品自产拍在线观看 | 国产 一区二区三区 在线 | 韩国一区视频 | 东方av免费在线观看 | 国产麻豆传媒 | 日韩在线高清免费视频 | 深爱综合网 | 国产一区二区三区 在线 | 在线免费高清 | 亚洲精品国产电影 | 999久久久欧美日韩黑人 | 狠色在线| 亚洲2019精品 | 又色又爽又激情的59视频 | 免费久久久久久 | 国产精品久久网站 | 四虎影视成人永久免费观看视频 | 色黄www小说 | 日韩午夜网站 | 色999精品 | 成年人免费在线 | 天天碰天天操视频 | 在线亚洲免费视频 | 国产中文欧美日韩在线 | 天天色综合1 | 亚洲免费在线看 | 五月婷婷国产 | 久久亚洲成人网 | 日韩黄色一区 | 国产精品免费在线视频 | 免费色视频| 精品亚洲视频在线观看 | 国产无限资源在线观看 | 久久人人插 | 久久久久电影 | 成人黄色在线观看视频 | 成年人免费在线观看网站 | www.xxxx欧美| 日韩一区视频在线 | 蜜臀av夜夜澡人人爽人人 | 中文字幕大全 | 九九综合久久 | 国产精品2020| 韩国av一区二区三区在线观看 | 国产专区视频 | 中文字幕免费一区 | 黄色aa久久 | 国产老熟| 91九色成人 | 欧美精品久久天天躁 | 麻豆观看 | 国产欧美综合在线观看 | 日本女人逼 | 99re热精品视频 | 精品久久久久久综合日本 | 国产精品免费大片视频 | 五月丁香 | 亚洲免费不卡 | 久章草在线观看 | 激情av五月婷婷 | 国产黄色片一级三级 | 国产 欧美 日本 | 国产精品成人国产乱一区 | 久久在线免费视频 | 国产伦理久久精品久久久久_ | 国产成人精品一区二区三区网站观看 | 国模精品一区二区三区 | 国产精品久久久久久婷婷天堂 | 久一在线 | 日韩欧美黄色网址 | 91福利试看 | 人人爽爽人人 | 国产福利91精品一区 | 国产 一区二区三区 在线 | 91精品综合在线观看 | 亚洲清纯国产 | 欧美日韩精品在线一区二区 | 成人久久18免费网站图片 | 久久999久久 | 337p日本大胆噜噜噜噜 | 亚洲精品国产成人 | 天天色天天草天天射 | 西西www444| 精品亚洲国产视频 | 国产成人一区三区 | 青青草国产精品 | 日韩有码网站 | 人人澡超碰碰97碰碰碰软件 | 婷婷色综合 | 亚洲免费av网站 | 免费视频三区 | 中文字幕国产视频 | 在线观看深夜视频 | 久久久人人人 | 婷婷夜夜| 十八岁以下禁止观看的1000个网站 | 国产精品日韩高清 | 国产成人777777| 国产精品第10页 | 超碰国产人人 | japanesexxx乱女另类 | 久久超碰99 | 不卡的av片 | 久久久精品在线观看 | 久久av免费观看 | 中文字幕在线观看免费高清电影 | 欧美日一级片 | 精品中文字幕在线 | 久草在线在线 | 午夜国产福利在线观看 | 天天干天天拍天天操 | 国产精品网在线观看 | 国产免费xvideos视频入口 | 亚洲精品五月天 | 亚洲成人精品 | 91看片在线看片 | 久久麻豆视频 | 午夜12点 | 国产成人一区二区三区 | 成av人电影| 最新日韩在线观看 | 欧美日韩在线播放一区 | 天天拍夜夜拍 | 九九九九九精品 | 91亚色免费视频 | 欧美-第1页-屁屁影院 | 日韩激情视频在线观看 | 亚洲伊人网在线观看 | 日本激情动作片免费看 | 中文字幕在线免费 | 亚洲国产精久久久久久久 | 久久久久99精品成人片三人毛片 | 久草香蕉在线 | 黄色在线成人 | 亚洲欧美日韩国产 | 日日夜夜天天 | 韩日在线一区 | 国产小视频福利在线 | 久久免费在线视频 | 五月天天在线 | 国产精品视频在线观看 | 99这里精品 | 日韩高清二区 | 中文字幕精品三级久久久 | 免费在线一区二区三区 | 日韩电影在线观看一区二区三区 | 欧美 国产 视频 | 久久精品视频在线观看 | 91麻豆网 | 欧美精品一区二区免费 | 国产视频2区 | 国产视频丨精品|在线观看 国产精品久久久久久久久久久久午夜 | 五月天久久激情 | 中文字幕一区二区三区乱码在线 | 久久tv | 国产精品a级 | 黄色网在线播放 | 日韩视频免费 | 国产精品99久久久久人中文网介绍 | 国产极品尤物在线 | 国产高清一区二区 | 久久久久久久久久久久久久电影 | 国产成人精品亚洲日本在线观看 | 日韩av成人免费看 | 欧美最猛性xxxx|