之前的文章說到了代理模式,代理模式的應用還是很多的。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