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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【转】 代理模式

發布時間:2023/12/10 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【转】 代理模式 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文鏈接:http://layznet.iteye.com/blog/1182924
一、代理概念
為某個對象提供一個代理,以控制對這個對象的訪問。 代理類和委托類有共同的父類或父接口,這樣在任何使用委托類對象的地方都可以用代理對象替代。代理類負責請求的預處理、過濾、將請求分派給委托類處理、以及委托類執行完請求后的后續處理。

圖1:代理模式

從圖中可以看出,代理接口(Subject)、代理類(ProxySubject)、委托類(RealSubject)形成一個“品”字結構。
根據代理類的生成時間不同可以將代理分為靜態代理和動態代理兩種。

下面以一個模擬需求說明靜態代理和動態代理:委托類要處理一項耗時較長的任務,客戶類需要打印出執行任務消耗的時間。解決這個問題需要記錄任務執行前時間和任務執行后時間,兩個時間差就是任務執行消耗的時間。

二、靜態代理
由程序員創建或工具生成代理類的源碼,再編譯代理類。所謂靜態也就是在程序運行前就已經存在代理類的字節碼文件,代理類和委托類的關系在運行前就確定了。
清單1:代理接口

Java代碼 ?
  • /**??
  • ?*?代理接口。處理給定名字的任務。?
  • ?*/??
  • public?interface?Subject?{??
  • ??/**?
  • ???*?執行給定名字的任務。?
  • ????*?@param?taskName?任務名?
  • ???*/??
  • ???public?void?dealTask(String?taskName);???
  • }??

  • 清單2:委托類,具體處理業務。

    Java代碼 ?
  • /**?
  • ?*?真正執行任務的類,實現了代理接口。?
  • ?*/??
  • public?class?RealSubject?implements?Subject?{??
  • ??
  • ?/**?
  • ??*?執行給定名字的任務。這里打印出任務名,并休眠500ms模擬任務執行了很長時間?
  • ??*?@param?taskName??
  • ??*/??
  • ???@Override??
  • ???public?void?dealTask(String?taskName)?{??
  • ??????System.out.println("正在執行任務:"+taskName);??
  • ??????try?{??
  • ?????????Thread.sleep(500);??
  • ??????}?catch?(InterruptedException?e)?{??
  • ?????????e.printStackTrace();??
  • ??????}??
  • ???}??
  • }??

  • 清單3:靜態代理類

    Java代碼 ?
  • /**?
  • ?* 代理類,實現了代理接口。?
  • ?*/??
  • public?class?ProxySubject?implements?Subject?{??
  • ?//代理類持有一個委托類的對象引用??
  • ?private?Subject?delegate;??
  • ???
  • ?public?ProxySubject(Subject?delegate)?{??
  • ??this.delegate?=?delegate;??
  • ?}??
  • ??
  • ?/**?
  • ??*?將請求分派給委托類執行,記錄任務執行前后的時間,時間差即為任務的處理時間?
  • ??*??
  • ??*?@param?taskName?
  • ??*/??
  • ?@Override??
  • ?public?void?dealTask(String?taskName)?{??
  • ??long?stime?=?System.currentTimeMillis();???
  • ??//將請求分派給委托類處理??
  • ??delegate.dealTask(taskName);??
  • ??long?ftime?=?System.currentTimeMillis();???
  • ??System.out.println("執行任務耗時"+(ftime?-?stime)+"毫秒");??
  • ????
  • ?}??
  • }??

  • 清單4:生成靜態代理類工廠

    Java代碼 ?
  • public?class?SubjectStaticFactory?{??
  • ?//客戶類調用此工廠方法獲得代理對象。??
  • ?//對客戶類來說,其并不知道返回的是代理類對象還是委托類對象。??
  • ?public?static?Subject?getInstance(){???
  • ??return?new?ProxySubject(new?RealSubject());??
  • ?}??
  • }??

  • 清單5:客戶類

    Java代碼 ?
  • public?class?Client1?{??
  • ??
  • ?public?static?void?main(String[]?args)?{??
  • ??Subject?proxy?=?SubjectStaticFactory.getInstance();??
  • ??proxy.dealTask("DBQueryTask");??
  • ?}???
  • ??
  • }??


  • 靜態代理類優缺點
    優點:業務類只需要關注業務邏輯本身,保證了業務類的重用性。這是代理的共有優點。
    缺點:
    1)代理對象的一個接口只服務于一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜態代理在程序規模稍大時就無法勝任了。
    2)如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的復雜度。

    三、動態代理
    動態代理類的源碼是在程序運行期間由JVM根據反射等機制動態的生成,所以不存在代理類的字節碼文件。代理類和委托類的關系是在程序運行時確定。

    1、先看看與動態代理緊密關聯的Java API。
    1)java.lang.reflect.Proxy
    這是 Java 動態代理機制生成的所有動態代理類的父類,它提供了一組靜態方法來為一組接口動態地生成代理類及其對象。
    清單6:Proxy類的靜態方法

    Java代碼 ?
  • //?方法?1:?該方法用于獲取指定代理對象所關聯的調用處理器??
  • static?InvocationHandler?getInvocationHandler(Object?proxy)???
  • ??
  • //?方法?2:該方法用于獲取關聯于指定類裝載器和一組接口的動態代理類的類對象??
  • static?Class?getProxyClass(ClassLoader?loader,?Class[]?interfaces)???
  • ??
  • //?方法?3:該方法用于判斷指定類對象是否是一個動態代理類??
  • static?boolean?isProxyClass(Class?cl)???
  • ??
  • //?方法?4:該方法用于為指定類裝載器、一組接口及調用處理器生成動態代理類實例??
  • static?Object?newProxyInstance(ClassLoader?loader,?Class[]?interfaces,?InvocationHandler?h)???


  • 2)java.lang.reflect.InvocationHandler
    這是調用處理器接口,它自定義了一個 invoke 方法,用于集中處理在動態代理類對象上的方法調用,通常在該方法中實現對委托類的代理訪問。每次生成動態代理類對象時都要指定一個對應的調用處理器對象。
    清單7:InvocationHandler的核心方法

    Java代碼 ?
  • //?該方法負責集中處理動態代理類上的所有方法調用。第一個參數既是代理類實例,第二個參數是被調用的方法對象??
  • //?第三個方法是調用參數。調用處理器根據這三個參數進行預處理或分派到委托類實例上反射執行??
  • Object?invoke(Object?proxy,?Method?method,?Object[]?args)???


  • 3)java.lang.ClassLoader
    這是類裝載器類,負責將類的字節碼裝載到 Java 虛擬機(JVM)中并為其定義類對象,然后該類才能被使用。Proxy 靜態方法生成動態代理類同樣需要通過類裝載器來進行裝載才能使用,它與普通類的唯一區別就是其字節碼是由 JVM 在運行時動態生成的而非預存在于任何一個 .class 文件中。
    每次生成動態代理類對象時都需要指定一個類裝載器對象

    2、動態代理實現步驟
    具體步驟是:
    a. 實現InvocationHandler接口創建自己的調用處理器
    b. 給Proxy類提供ClassLoader和代理接口類型數組創建動態代理類
    c. 以調用處理器類型為參數,利用反射機制得到動態代理類的構造函數
    d. 以調用處理器對象為參數,利用動態代理類的構造函數創建動態代理類對象
    清單8:分步驟實現動態代理

    Java代碼 ?
  • //?InvocationHandlerImpl?實現了?InvocationHandler?接口,并能實現方法調用從代理類到委托類的分派轉發??
  • //?其內部通常包含指向委托類實例的引用,用于真正執行分派轉發過來的方法調用??
  • InvocationHandler?handler?=?new?InvocationHandlerImpl(..);???
  • ??
  • //?通過?Proxy?為包括?Interface?接口在內的一組接口動態創建代理類的類對象??
  • Class?clazz?=?Proxy.getProxyClass(classLoader,?new?Class[]?{?Interface.class,?...?});???
  • ??
  • //?通過反射從生成的類對象獲得構造函數對象??
  • Constructor?constructor?=?clazz.getConstructor(new?Class[]?{?InvocationHandler.class?});???
  • ??
  • //?通過構造函數對象創建動態代理類實例??
  • Interface?Proxy?=?(Interface)constructor.newInstance(new?Object[]?{?handler?});???


  • Proxy類的靜態方法newProxyInstance對上面具體步驟的后三步做了封裝,簡化了動態代理對象的獲取過程。
    清單9:簡化后的動態代理實現

    Java代碼 ?
  • //?InvocationHandlerImpl?實現了?InvocationHandler?接口,并能實現方法調用從代理類到委托類的分派轉發??
  • InvocationHandler?handler?=?new?InvocationHandlerImpl(..);???
  • ??
  • //?通過?Proxy?直接創建動態代理類實例??
  • Interface?proxy?=?(Interface)Proxy.newProxyInstance(?classLoader,???
  • ?????new?Class[]?{?Interface.class?},??handler?);???


  • 3、動態代理實現示例
    清單10:創建自己的調用處理器

    Java代碼 ?
  • /**?
  • ?*?動態代理類對應的調用處理程序類?
  • ?*/??
  • public?class?SubjectInvocationHandler?implements?InvocationHandler?{??
  • ???
  • ?//代理類持有一個委托類的對象引用??
  • ?private?Object?delegate;??
  • ???
  • ?public?SubjectInvocationHandler(Object?delegate)?{??
  • ??this.delegate?=?delegate;??
  • ?}??
  • ???
  • ?@Override??
  • ?public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{??
  • ??long?stime?=?System.currentTimeMillis();???
  • ??//利用反射機制將請求分派給委托類處理。Method的invoke返回Object對象作為方法執行結果。??
  • ??//因為示例程序沒有返回值,所以這里忽略了返回值處理??
  • ??method.invoke(delegate,?args);??
  • ??long?ftime?=?System.currentTimeMillis();???
  • ??System.out.println("執行任務耗時"+(ftime?-?stime)+"毫秒");??
  • ????
  • ??return?null;??
  • ?}??
  • }???

  • 清單11:生成動態代理對象的工廠,工廠方法列出了如何生成動態代理類對象的步驟。

    Java代碼 ?
  • /**?
  • ?*?生成動態代理對象的工廠.?
  • ?*/??
  • public?class?DynProxyFactory?{??
  • ?//客戶類調用此工廠方法獲得代理對象。??
  • ?//對客戶類來說,其并不知道返回的是代理類對象還是委托類對象。??
  • ?public?static?Subject?getInstance(){???
  • ??Subject?delegate?=?new?RealSubject();??
  • ??InvocationHandler?handler?=?new?SubjectInvocationHandler(delegate);??
  • ??Subject?proxy?=?null;??
  • ??proxy?=?(Subject)Proxy.newProxyInstance(??
  • ????delegate.getClass().getClassLoader(),???
  • ????delegate.getClass().getInterfaces(),???
  • ????handler);??
  • ??return?proxy;??
  • ?}??
  • }??


  • 清單12:動態代理客戶類

    Java代碼 ?
  • public?class?Client?{??
  • ??
  • ?public?static?void?main(String[]?args)?{??
  • ??
  • ??Subject?proxy?=?DynProxyFactory.getInstance();??
  • ??proxy.dealTask("DBQueryTask");??
  • ?}???
  • ??
  • }??


  • 4、動態代理機制特點?
    首先是動態生成的代理類本身的一些特點。1)包:如果所代理的接口都是 public 的,那么它將被定義在頂層包(即包路徑為空),如果所代理的接口中有非 public 的接口(因為接口不能被定義為 protect 或 private,所以除 public 之外就是默認的 package 訪問級別),那么它將被定義在該接口所在包(假設代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理類所在的包就是 com.ibm.developerworks),這樣設計的目的是為了最大程度的保證動態代理類不會因為包管理的問題而無法被成功定義并訪問;2)類修 飾符:該代理類具有 final 和 public 修飾符,意味著它可以被所有的類訪問,但是不能被再度繼承;3)類名:格式是“$ProxyN”,其中 N 是一個逐一遞增的阿拉伯數字,代表 Proxy 類第 N 次生成的動態代理類,值得注意的一點是,并不是每次調用 Proxy 的靜態方法創建動態代理類都會使得 N 值增加,原因是如果對同一組接口(包括接口排列的順序相同)試圖重復創建動態代理類,它會很聰明地返回先前已經創建好的代理類的類對象,而不會再嘗試去創 建一個全新的代理類,這樣可以節省不必要的代碼重復生成,提高了代理類的創建效率。4)類繼承關系:該類的繼承關系如圖:

    圖2:動態代理類的繼承關系



    由圖可見,Proxy 類是它的父類,這個規則適用于所有由 Proxy 創建的動態代理類。而且該類還實現了其所代理的一組接口,這就是為什么它能夠被安全地類型轉換到其所代理的某接口的根本原因。

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

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

    最后再來了解一下異常處理方面的特點。從調用處理器接口聲明的方法中可以看到理論上它能夠拋出任何類型的異常,因為所有的異常都繼承于 Throwable 接口,但事實是否如此呢?答案是否定的,原因是我們必須遵守一個繼承原則:即子類覆蓋父類或實現父接口的方法時,拋出的異常必須在原方法支持的異常列表之 內。所以雖然調用處理器理論上講能夠,但實際上往往受限制,除非父接口中的方法支持拋 Throwable 異常。那么如果在 invoke 方法中的確產生了接口方法聲明中不支持的異常,那將如何呢?放心,Java 動態代理類已經為我們設計好了解決方法:它將會拋出 UndeclaredThrowableException 異常。這個異常是一個 RuntimeException 類型,所以不會引起編譯錯誤。通過該異常的 getCause 方法,還可以獲得原來那個不受支持的異常對象,以便于錯誤診斷。

    5、動態代理的優點和美中不足
    優點:
    動態代理與靜態代理相比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理 (InvocationHandler.invoke)。這樣,在接口方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進 行中轉。在本示例中看不出來,因為invoke方法體內嵌入了具體的外圍業務(記錄任務處理前后時間并計算時間差),實際中可以類似Spring AOP那樣配置外圍業務。

    美中不足:
    誠然,Proxy 已經設計得非常優美,但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支持 interface 代理的桎梏,因為它的設計注定了這個遺憾。回想一下那些動態生成的代理類的繼承關系圖,它們已經注定有一個共同的父類叫 Proxy。Java 的繼承機制注定了這些動態代理類們無法實現對 class 的動態代理,原因是多繼承在 Java 中本質上就行不通。

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

    轉載于:https://www.cnblogs.com/bingzhikun/p/4797679.html

    總結

    以上是生活随笔為你收集整理的【转】 代理模式的全部內容,希望文章能夠幫你解決所遇到的問題。

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