Java設計模式之代理初探
java設計模式之靜態代理,動態代理
代理模式是對象的結構模式。代理模式給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。意思就是一個體或者機構代表另一個體或者機構執行一些行為。在一些情況下,一個用戶端不想或者不能夠直接引用一個對象,而代理對象可以在用戶端和目標對象之間起到中介的作用。
靜態代理
情境:
比如你要買一張飛機票,你的行為是要買機票,買機票可以通過第三方代理平台,比如阿里,去哪兒,攜程這些平台幫你買,這種平台就充當了代理角色,這樣的話你就不用直接在航空公司買票了。只需要將你行為需求告訴代理角色即可。
代碼
1.建立行為介面
public interface BuyTicketInterface { //買票的行為 void butTicket();}
2.建立用戶端,用戶端要實現介面中定義的行為,我要買票。然後執行自己的行為。
public class TravelPeople implements BuyTicketInterface { //用戶端的行為和需求,是這樣的。 public void butTicket() { System.out.println("買一張5月1號重慶江北機場飛往北京首都機場的航班,時間14:00到24:00之間"); }}
3.建立代理端功能,代理端也要實現買票的行為,不然怎麼完成用戶端的要求,代理執行買票的過程是替將用戶端的執行的。代碼如下。
public class BuyTicketProxy implements BuyTicketInterface{ private TravelPeople travelPeople; // public BuyTicketProxy(){ this.travelPeople = new TravelPeople(); } /** * 執行的時候調用的是代理的行為,代理是替用戶端辦事的。 */ public void buyTicket() { if(travelPeople != null){ travelPeople.buyTicket(); } }}
4.使用代理來購票。終端行為和代理方處理,實現購片的功能;
代碼如下:
public class TicketService { public static void main(String[] args) { TicketService.startBuyTicket(); } //開始買票,看似交易的是代理和票務中心交易,其實執行的是用戶端的交易。 public static void startBuyTicket(){ BuyTicketInterface buyTick = new BuyTicketProxy(); buyTick.buyTicket(); }}
運行結果
買一張5月1號重慶江北機場飛往北京首都機場的航班,時間14:00到24:00之間
靜態代理模式優缺點:
優點:業務類只需要關注商務邏輯本身,保證了業務類的重用性。這是代理的共有優點,同時降低了商務邏輯的耦合性。
缺點:
1)代理對象的一個介面只服務於一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜態代理在程式規模稍大時就無法勝任了。
2)如果介面增加一個方法,除了所有實作類別需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的複雜度。
那麼問題來了,如果要按照上述的方式(靜態代理)使用代理模式,那麼真實角色必須是實現已經存在的,並將其作為代理對象的內部屬性。但是實際使用時,一個真實角色必須對應一個代理角色,但如果大量使用會導致類的急劇膨脹;此外,如果事先並不知道真實角色,該如何使用代理呢?這個問題可以通過Java的動態代理類來解決。
動態代理
動態代理類的源碼是在程式運行期間由JVM根據反射等機制動態產生,所以不存在代理類的位元組碼檔案。代理類和委託類的關係是在程式運行時確定。 通過Java的反射機制來實現。
1. 先看看與動態代理緊密關聯的Java API。
1)java.lang.reflect.Proxy
這是 Java 動態代理機制產生的所有動態代理類的父類,它提供了一組靜態方法來為一組介面動態地組建代理程式類及其對象。
proxy的代碼清單如下
// 方法 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 方法,用於集中處理在動態代理類對象上的方法調用,通常在該方法中實現對委託類的代理訪問。每次產生動態代理類對象時都要指定一個對應的調用處理器對象。
InvocationHandlerd代碼清單如下:
// 該方法負責集中處理動態代理類上的所有方法調用。第一個參數既是代理類執行個體,第二個參數是被調用的方法對象 // 第三個方法是調用參數。調用處理器根據這三個參數進行預先處理或指派到委託類執行個體上反射執行 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. 以調用處理器對象為參數,利用動態代理類的建構函式建立動態代理類對象
// 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 });
3.動態代理實現樣本
建立介面;
public interface BuyTicketInterface { void buyTicket();}
建立動態代理類對應的調用處理常式類
public class BuyTicketHandler implements InvocationHandler { /** * 動態代理類對應的調用處理常式類 */ private Object orginObject; //代理類持有一個委託類的對象引用 public BuyTicketHandler(Object orginObject){ this.orginObject = orginObject; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result ; //調用方法之前; doBefore(); //利用反射機制將請求指派給委託類處理。Method的invoke返回Object對象作為方法執行結果。 //因為樣本程式沒有傳回值,所以這裡忽略了傳回值處理 result = method.invoke(this.orginObject, args); doAfter(); return result ; } private void doBefore(){ System.out.println("開始買票"); } private void doAfter(){ System.out.println("調用買票"); }}
產生動態代理對象的工廠,Factory 方法列出了如何產生動態代理類對象的步驟。
/** * 產生動態代理對象的工廠. */public class DynProxyFactory { //客戶類調用此Factory 方法獲得代理對象。 //對客戶類來說,其並不知道返回的是代理類對象還是委託類對象。 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; }}
動態代理客戶類
public class BuyTicketProxy { //客戶類調用此Factory 方法獲得代理對象。 //對客戶類來說,其並不知道返回的是代理類對象還是委託類對象。 public static BuyTicketInterface startBuy(){ //建立待代理的對象 BuyTicketInterface buyTicket = new TravelPeople(); //建立動態代理 InvocationHandler handler = new BuyTicketHandler(buyTicket); //通過反射來擷取對象。 BuyTicketInterface buy = (BuyTicketInterface)Proxy.newProxyInstance(buyTicket.getClass().getClassLoader(), buyTicket.getClass().getInterfaces(), handler); return buy; }}
建立測試;
BuyTicketInterface buy = BuyTicketProxy.startBuy(); buy.buyTicket();
運行結果:
開始買票買一張5月1號重慶江北機場飛往北京首都機場的航班,時間14:00到24:00之間調用買票
總結:首先是動態產生的代理類本身的一些特點。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 值增加,原因是如果對同一組介面(包括介面排列的順序相同)試圖重複建立動態代理類,它會很聰明地返回先前已經建立好的代理類的類對象,而不會再嘗試去建立一個全新的代理類,這樣可以節省不必要的代碼重複產生,提高了代理類的建立效率。
優點:
動態代理與靜態代理相比較,最大的好處是介面中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在介面方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉。在本樣本中看不出來,因為invoke方法體內嵌入了具體的外圍業務(記錄任務處理前後時間並計算時間差)。
缺點:
Proxy 已經設計得很完美了,但是還是有局限,就是不能擺脫 interface 代理的桎梏,因為它的設計註定了這個遺憾。回想一下那些動態產生的代理類的繼承關係圖,它們已經註定有一個共同的父類叫 Proxy。Java 的繼承機制註定了這些動態代理類們無法實現對 class 的動態代理,原因是多繼承在 Java 中本質上就行不通。
有很多條理由,人們可以否定對 class 代理的必要性,但是同樣有一些理由,相信支援 class 動態代理會更美好。介面和類的劃分,本就不是很明顯,只是到了 Java 中才變得如此的細化。如果只從方法的聲明及是否被定義來考量,有一種兩者的混合體,它的名字叫抽象類別。實現對抽象類別的動態代理,相信也有其內在的價值。此外,還有一些曆史遺留的類,它們將因為沒有實現任何介面而從此與動態代理永世無緣。如此種種,不得不說是一個小小的遺憾。
最後,代理設計模式結合項目自身的情況而定,做到將設計模式的設計功能發揮到最大化。使項目,易擴充,耦合性低。才是前進的方向。