代理模式及其應用


代理模式是一種應用十分廣泛的結構型模式,在spring中,就有使用了代理模式,今天我們來總結一下代理模式,主要分析其原理,還有在特定場景下是怎樣應用的。

意圖:為其他對象提供一種代理以控制對這個對象的訪問。

主要解決:在直接訪問對象時帶來的問題,比如說:要訪問的對象在遠程的機器上。在面向對象系統中,有些對象由於某些原因(比如對象創建開銷很大,或者某些操作需要安全控制,或者需要進程外的訪問),直接訪問會給使用者或者系統結構帶來很多麻煩,我們可以在訪問此對象時加上一個對此對象的訪問層。

何時使用:想在訪問一個類時做一些控制。

如何解決:增加中間層。

關鍵代碼:實現與被代理類組合。

注意:與適配器模式的區別,適配器模式主要改變所考慮對象的接口,而代理模式不能改變所代理類的接口。與裝飾器模式的區別,裝飾器模式是為了增強功能,代理模式是為了加以控制。

我們來看一個例子:

我們創建一個image接口來抽象display這一行為,然后通過proxyImage來獲取要加載的圖像並顯示。

1.創建接口代碼:

1 public interface Image {
2    void display();
3 }

2.創建實體類(被代理類也叫真實類):

 1 public class RealImage implements Image {
 2 
 3    private String fileName;
 4 
 5    public RealImage(String fileName){
 6       this.fileName = fileName;
 7       loadFromDisk(fileName);
 8    }
 9 
10    @Override
11    public void display() {
12       System.out.println("Displaying " + fileName);
13    }
14 
15    private void loadFromDisk(String fileName){
16       System.out.println("Loading " + fileName);
17    }
18 }

3.創建代理類:

 1 public class ProxyImage implements Image{
 2 
 3    private RealImage realImage;   //持有一個指向真實類的引用  4    private String fileName;
 5 
 6    public ProxyImage(String fileName){
 7       this.fileName = fileName;
 8    }
 9 
10    @Override
11    public void display() {   //這里實現了對圖片的延時加載,在代理類被執行的時候,圖片才加載到內存 12       if(realImage == null){
13          realImage = new RealImage(fileName);
14       }
15       realImage.display();
16    }
17 }

4.調用類:

 1 public class ProxyPatternDemo {
 2     
 3    public static void main(String[] args) {
 4       Image image = new ProxyImage("1.jpg");
 5 
 6       //圖像將從磁盤加載
 7       image.display(); 
 8       System.out.println("");
 9       //圖像將不從磁盤加載
10       image.display();     
11    }
12 }

注:以上摘抄自http://www.runoob.com/design-pattern/proxy-pattern.html

5.應用:

在數據庫連接池當中,數據庫的連接在整個應用的啟動期間,幾乎是不關閉的。

但我們在編寫代碼的時候,即使用到了連接池,也會去執行connection.close()方法,這樣頻繁的關閉創建連接,十分的浪費資源,這就帶來了問題,我們執行close() 方法,真的是關閉了這個連接嗎?

我們以c3p0連接池為例,研究一下里面到底是如何執行close()方法的:

首先,我們先看一下在ComboPooledDataSource中取得的數據庫連接是什么東西:

 1 import java.beans.PropertyVetoException;
 2 import java.sql.SQLException;
 3 
 4 /**
 5  * Created by jy on 2018/3/27.
 6  */
 7 public class c3p0Test {
 8     public static void main(String[] args) throws SQLException, PropertyVetoException {
 9         ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
10         comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
11         comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/qingguodb?characterEncoding=utf-8");
12         comboPooledDataSource.setPassword("root");
13         comboPooledDataSource.setUser("root");
14         System.out.println(comboPooledDataSource.getConnection().getClass());
15     }
16 }

我們看一下執行結果:

我們來看一下這個類的層次結構:

可以看到,這個類實現了Connection接口。也就是connection的代理類,我們看一下這個類中的close() 方法:

 1 public final class NewProxyConnection implements Connection, C3P0ProxyConnection
 2 {
 3     protected Connection inner;
 4     //....
 5 public synchronized void close() throws SQLException
 6     {
 7         try
 8         {
 9             if (! this.isDetached())
10             {
11                 NewPooledConnection npc = parentPooledConnection;
12                 this.detach();
13                 npc.markClosedProxyConnection( this, txn_known_resolved );  //同步確認連接關閉 14                 this.inner = null;    //直接置為null,並沒有調用inner.close() 15                           //.....
16             }
17     }

 

我們看到代碼中是直接把newProxyConnection中的屬性inner直接置為空,並沒有close 真正的connection,所以,我們可以知道,在數據庫連接池C3P0中,底層通過使用了代理模式,生成代理類,來管理真正的數據庫連接,當我們執行close()方法時,實際上是執行了代理類的close()方法,並不會真正的關閉數據庫連接。

我們上面總結的代理模式的代理類是硬編碼的,一般來說,代理類持有一個被代理類的對象的引用,我們關心的業務邏輯,放在代理類中去執行,如果有大量的類需要被代理,那我們是不是就要寫大量對應的代理類呢?於是,上面這個稱作是靜態代理,那對應的就有動態代理。我們來看看一下什么是動態代理。

在java的動態代理機制中,有一個重要的接口:InvocationHandler接口,它為每個代理類都關聯了一個handler,當我們通過代理對象調用一個方法的時候,就會被轉發到InvocationHandler這個接口的invoke方法來進行調用,我們來看看這個方法:

1 Object invoke(Object proxy, Method method, Object[] args) throws Throwable

其中:proxy指的是代理對象,method指的我們要調用被代理對象的某個方法,args指的是調用被代理對象方法時需要傳入的參數。

 

我們來通過一個例子看看到底如何實現動態代理:

1.定義主題接口:

1 public interface Subject {
2     void sayHello();
3 }

2.定義真實主題(被代理的類):

1 public class RealSubject implements Subject{
2 
3     @Override
4     public void sayHello() {
5         System.out.println("hello world");
6     }
7 }

3.定義代理生成工廠:

 1 public class ProxyFactory{
 2 
 3     //維護一個目標對象
 4     private Object target;
 5 
 6     //為目標對象賦值
 7     public ProxyFactory(Object target){
 8         this.target=target;
 9     }
10 
11     //給目標對象生成代理對象
12     public Object getProxyInstance(){
13         //傳入被代理類的類加載器,被代理類實現的接口中的方法,需要添加的業務邏輯,返回一個代理類
14         return Proxy.newProxyInstance(
15                 target.getClass().getClassLoader(),
16                 target.getClass().getInterfaces(),
17                 new InvocationHandler() {
18                     @Override
19                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
20                         //目標對象執行前加入業務邏輯
21                         System.out.println("我要開始說話了:");
22                         //執行目標對象方法
23                         Object value = method.invoke(target, args);
24                         //目標對象執行后加入邏輯
25                         System.out.println("我說完了");
26                         return value;
27                     }
28                 }
29         );
30     }
31 
32 }

4.調用函數:

1 public class Client {
2     public static void main(String[] args) {
3         Subject realSubject = new RealSubject();
4         ((Subject)new ProxyFactory(realSubject).getProxyInstance()).sayHello();
5     }
6 }

我們看一下輸出:

我們發現,在執行sayHello()前后的業務都被織入了進去。原來這就是spring aop的簡單實現。

關於代理模式我想就總結到這里,至於動態代理的實現原理及實現方式,這些內容與代理模式本身沒有多大的關聯,我想放到下一片文章中去總結。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM