代理模式是一種應用十分廣泛的結構型模式,在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的簡單實現。
關於代理模式我想就總結到這里,至於動態代理的實現原理及實現方式,這些內容與代理模式本身沒有多大的關聯,我想放到下一片文章中去總結。
