Java動態代理

之前的文章說到了代理模式,代理模式的應用還是很多的。Java這方面還支持動態代理,下面我們慢慢來說。

一.普通代理模式

如果客戶端不想或者不能直接訪問被調用對象——這種情況有很多原因,比如需要創建一個系統開銷很大的對象,或者被調用對象在遠程主機上,或者目標對象的功能還不足以滿足需求……,而是額外創建一個代理對象返回給客戶端使用,那麼這種設計方式就是代理模式。相對於Java動態代理,普通代理模式也叫做靜態代理模式。這裏要說的例子就是Java代理模式的一種應用。

代理類和被代理的類都要實現相同的接口。

public interface Image 
{ 
     void show(); 
}

// 使用該 BigImage 模擬一個很大圖片
public class BigImage implements Image 
{ 
     public BigImage() 
    { 
         try 
        { 
             // 程序暫停 3s 模式模擬系統開銷 
	       Thread.sleep(3000); 
             System.out.println("圖片裝載成功 ..."); 
        } 
        catch (InterruptedException ex) 
        { 
             ex.printStackTrace(); 
        } 
    } 
    // 實現 Image 裏的 show() 方法
    public void show() 
    { 
        System.out.println("繪製實際的大圖片"); 
    } 
}

public class ImageProxy implements Image 
{ 
    
    private Image image;   // 組合一個 image 實例,作爲被代理的對象
    
    public ImageProxy(Image image) // 使用抽象實體來初始化代理對象
   { 
       this.image = image; 
   } 
   /** 
    * 重寫 Image 接口的 show() 方法
    * 該方法用於控制對被代理對象的訪問,
    * 並根據需要負責創建和刪除被代理對象
    */ 
    public void show() 
    { 
 
       if (image == null) // 只有當真正需要調用 image 的 show 方法時才創建被代理對象
       { 
          image = new BigImage(); 
       } 
       image.show(); 
    } 
}
//測試類 客戶端
public class BigImageTest 
{ 
     public static void main(String[] args) 
     { 
          long start = System.currentTimeMillis(); 
         // 程序返回一個 Image 對象,該對象只是 BigImage 的代理對象
          Image image = new ImageProxy(null); 
          System.out.println("系統得到 Image 對象的時間開銷 :" + (System.currentTimeMillis() - start)); 
     // 只有當實際調用 image 代理的 show() 方法時,程序纔會真正創建被代理對象。
         image.show(); 
     } 
}


看到如圖 6 所示的運行結果,讀者應該能認同:使用代理模式提高了獲取 Image 對象的系統性能。但可能有讀者會提出疑問:程序調用 ImageProxy 對象的 show() 方法時一樣需要創建 BigImage 對象啊,系統開銷並未真正減少啊?只是這種系統開銷延遲了而已啊?

上面程序初始化 image 非常快,因爲程序並未真正創建 BigImage 對象,只是得到了 ImageProxy 代理對象——直到程序調用 image.show() 方法時,程序需要真正調用 BigImage 對象的 show() 方法,程序此時才真正創建 BigImage 對象。運行上面程序,看到如圖 6 所示的結果。

圖 . 使用代理模式提高性能


我們可以從如下兩個角度來回答這個問題:

  • 把創建 BigImage 推遲到真正需要它時才創建,這樣能保證前面程序運行的流暢性,而且能減少 BigImage 在內存中的存活時間,從宏觀上節省了系統的內存開銷。
  • 有些情況下,也許程序永遠不會真正調用 ImageProxy 對象的 show() 方法——意味着系統根本無須創建 BigImage 對象。在這種情形下,使用代理模式可以顯著地提高系統運行性能。

與此完全類似的是,Hibernate 也是通過代理模式來“推遲”加載關聯實體的時間,如果程序並不需要訪問關聯實體,那程序就不會去抓取關聯實體了,這樣既可以節省系統的內存開銷,也可以縮短 Hibernate 加載實體的時間。Hibernate 的延遲加載(lazy load)本質上就是代理模式的應用,我們在過去的歲月裏就經常通過代理模式來降低系統的內存開銷、提升應用的運行性能,並結合了 Javassist 或 CGLIB 來動態地生成代理對象。Hibernate 對於 Set 屬性延遲加載關鍵就在於 PersistentSet 實現類。在延遲加載時,開始 PersistentSet 集合裏並不持有任何元素。但 PersistentSet 會持有一個 Hibernate Session,它可以保證當程序需要訪問該集合時“立即”去加載數據記錄,並裝入集合元素。與 PersistentSet 實現類類似的是,Hibernate 還提供了 PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等實現類,它們的功能與 PersistentSet 的功能大致類似。

Hibernate中默認採用延遲加載的情況主要有以下幾種

1 當調用session上的load()加載一個實體時,會採用延遲加載。

2 當session加載某個實體時,會對這個實體中的集合屬性值採用延遲加載

3 當session加載某個實體時,會對這個實體所有單端關聯的另一個實體對象採用延遲加載,也就是使用 <many-to-one.../> 或 <one-to-one.../> 映射關聯實體的情形。


二.動態代理

Java如果使用上面的這樣靜態代理,那每一個被代理的對象都需要創建一個代理類,會導致代碼變得重複和冗餘,如果能在運行的時候在創建一個代理類,它在作爲任何被代理對象的代理類,這就用要用到動態代理。

還有一個用處,如果一個系統出現了很多的相同代碼段,則可以將相同代碼段定義爲一個方法,讓其他需要用到該方法的程序調用它-----這起到了解耦的作用,大大降低軟件系統後期維護的複雜度。但是還是和特定的方法耦合了,這就要用到動態代理來解決問題。

注意JDK動態代理只能爲接口創建,這是它的一個不足。例子如下:

import java.lang.reflect.*;
import java.lang.reflect.Proxy;
//接口
interface Dog{
	void info();
	void run();
}
//被代理的類,正真輸出方法的對象。還可以有多個現實
class MuDog implements Dog{
	public void info()
	{
		System.out.println("我是一隻牧羊犬");
	}
	public void run()
	{
		System.out.println("我跑的快");
	}
}
//動態代理類,運行時才加載需要代理的對象
class ProxyImp implements InvocationHandler{
	private Object target;  //定義爲Object
	//運行時在取得需要代理的類
	public Object bind(Object target)
	{
		this.target=target;
		//返回一個代理類,運行方法時是調用了下面的invoke方法
		return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);   
	}
	
	@Override
	public Object invoke(Object proxy,Method method,Object[] args)throws Exception
	{
		System.out.println("--------動態代理開始--------");
		Object result=method.invoke(target,args);
		System.out.println("--------代理結束--------");
		return result;
	}
}

public class Test{
	public static void main(String[] args){
		ProxyImp proxy=new ProxyImp();
		Dog dog=(Dog)proxy.bind(new MuDog()); //不僅可以有MuDog,還可以有不同的實現,卻只有一個代理類
		dog.info();
		dog.run();
	}
}

結果:


上面不僅讓對象進行了解耦,還可以在invoke方法裏面進行其他通用的處理,這也叫做AOP(面向切面編程)。基於反射的動態代理,靈活的實現解耦。沒有實現接口的類就不能使用動態代理,可以使用Cglib來實現動態代理 。Cglib是採用動態創建子類的方法,代理類爲動態創建的子類,對於final方法,無法進行代理。


參考:

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

http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章