一、
proxy模式簡介
GoF介紹了proxy模式。代理對象為其他對象提供一種代理以控制對這個對象的訪問。它靜態結構如下:
Client
需要訪問
RealSubject
時,它實際訪問的是
Proxy
對象,而后
Proxy
對象將請求委托給
RealSubject
。
RealSubject
實現了主要的邏輯,
Proxy
對象可以在處理請求之前、之后作額外的處理。可以看出,
Proxy
和
RealSubject
實現了同樣的接口,這樣
Client
才可以調用
RealSubject
實現的所有
Subject
的方法。
我們在實現
Proxy
時,如果使用的是
C++
語言,我們可以重載操作符
->
來實現代理。優點是實現簡單,缺點是它不能區別對待不同的請求。當然也可以是用普通的形式,創建一個代理類,實現接口,并將調用委托給被代理的對象。
如果使用的是
Java
語言,我們當然可以使用普通的形式來實現
Proxy
模式。但是
JDK1.3
引入了
dynamic proxy
,它允許我們更容易的實現代理。
二、
JDK中的Dynamic Proxy介紹
它由
java.lang.reflect.Proxy
、
java.lang.reflect.InvocationHandler
等組成。
Proxy
類擁有一個
protected
的
InvocationHandler
類型的成員變量。
它只能代理
Interface。
它的基本思想如下:
1.????
代理類由
Proxy
的靜態方法
getProxyClass
來動態的創建出來。該方法所需要的一個參數為它所代理的接口數組。創建出來的代理類實現了所有的接口,并且繼承了
Proxy
。
2.????
代理類實現的接口方法的處理邏輯為,調用父類的
InvocationHandler
類型的成員變量的
invoke
方法。
由此可以看出,必須讓
Proxy
對象擁有一個正確的
InvocationHandler
的實現。
Proxy
對象由
Proxy
的靜態成員函數
newProxyInstance
來創建,該函數的最后一個參數為
InvocationHandler
類型。動態生成的代理類實現的所有接口方法都被委托給
InvocationHandler
接口的
invoke
方法。
三、
例子代碼
假如有如下接口:
interface Foo {
?void f(String s);
?void g(int i);
?String h(int i, String s);
}
并且有一個實現
class Implement implements Foo {
?? …
}
現在我們想在
Foo
接口的每個方法調用時,加入日至。一個很簡單很直觀的方法如下:
class LogProxy implements Foo {
private Foo delegate ;
?? public LogProxy( Foo foo ) {
????????? delegate = foo ;
}
public String h(int i, String s) {
????? System.out.println(“call h begin ”) ;
????? String result = delegate.h( i , s ) ;
????? System.out.println(“call h end ”) ;
????? Return result ;
}
…
}
new LogProxy( new Implement()
).h( 10 , “str”);
可以看出這樣的實現代碼很多,而且幾乎都是相同的。當然可以編寫程序來寫出這樣的代碼,但是我們如果使用
JDK1.3
引入的
dynamic proxy
,那么情況就完全不同了。
四、
dynamic proxy實現Log的代碼
1.??????
編寫
InvocationHandler
的子類,來攔截所有的方法調用。
class LogProxy implements InvocationHandler {
????? public LogProxy( Object object ) { obj = object ; }
????? public Object invoke(Object proxy, Method method, Object[] args)
????? throws Throwable {??
String methodName = method.getName() ;
System.out.println("call " + methodName?+ “ begin “ ) ;???????
?????? ??? Object result = method.invoke( obj , args )?;
System.out.println("call " + methodName + “ end“ ) ;???????
??? ?????? Return result ;
????? }
????? public Object obj = null ;
}
2.??????
使用
Proxy的靜態方法來創建代理類。?
LogProxy dp = new LogProxy( new Implment() ) ;
Foo proxy = (Foo) Proxy.newProxyInstance(
??????????? ????????// [1] class loader
????? ????????????? Foo.class.getClassLoader(),
?????????????????? // [2] interface's Class array
?????? ?????????????????? new Class[]{ Foo.class },
?????? ?????????????????? // [3] InvocationHandler
?????????????????? dp ) ;
3.??????
客戶在代理類上調用方法
proxy.
h( 10 , “str”);
可以看出,如果接口中有很多方法,那么使用
dynamic proxy
是很合適的,但是如果接口只有很少的方法,可能使用普通的方法更直觀,也更簡單。
五、
應用例子
如果我們設計一個數據庫連接池,接口如下:
interface DBConnectionPool {
??????????? public java.sql.Connection getConnection() ;
??????????? public void releaseConnection( java.sql.Connection conn ) ;
}
class DBConnectionPoolImpl implements DBConnectionPool {
??????????? …
}
?
那么一個可能的用戶調用序列如下:
void getData() {
??????????? DBConnectionPoolImpl?cpi = new DBConnectionPoolImpl() ;
??????????? Connection conn = cpi.getConnection() ;
??????????? // use conn to retrieve data from db
??????????? …
??????????? cpi. releaseConnection( conn ) ;
???????????
}
藍色的代碼表示了將連接還給連接池,因為所有的連接都是由連接池來管理的。但是這樣的代碼對用戶來講可能不太習慣,而且迫使用戶這樣編寫代碼,用戶會意識到
cpi
對連接作了特殊的處理。
???????????
一個更好的方法是調用
Connection
接口的
close
方法。這樣的代碼如下:
void getData() {
??????????? DBConnectionPoolImpl?cpi = new DBConnectionPoolImpl() ;
??????????? Connection conn = cpi.getConnection() ;
??????????? // use conn to retrieve data from db
??????????? …
??????????? conn.close() ;
}
這樣更符合普通用戶的編碼習慣。但是可以這么編碼的前提是:
1、
close
函數要將連接對象還給連接池,而不是關閉物理的數據庫連接。
2、
所有
Connection
的其他函數必須能夠正常工作。
也就是說需要特殊處理
close
函數,而對其他函數直接進行轉發就可以了。
用最直接的方法實現如下:
class ConnectionProxy implements Connection {
??????????? private Connection realConn ;
??????????? private DBConnectionPool dbcp ;
??????????? public ConnectionProxy( Connection conn?,?DBConnectionPool pool ) {
??????????????????????? realConn = conn ;
??????????????????????? dbcp = pool ;
}
?
public void close() throws SQLException {
????????? dbcp. releaseConnection( realConn ) ;
}
?
public PreparedStatement prepareStatement(String sql, String columnNames[]) throws SQLException {
?????????
????????? return realConn.prepareStatement( sql , columnNames ) ;
}
//??????? 所有的其他Connection接口中的方法轉發
…
}
可以看出這樣的實現代碼很多,而且幾乎都是相同的。當然可以編寫程序來寫出這樣的代碼,如果使用
DynamicProxy
,那么整個實現就比較優雅了。
?
Classs ConnectionProxy
InvocationHandler {
?? Connection conn ;
??
DBConnectionPool cp ;
Public ConnectionProxy( Connection conn ,
DBConnectionPool cp
) {
This.conn = conn ;
This.cp = cp ;
}
public Object invoke(Object proxy, Method method, Object[] args)
????? throws Throwable {
???????????? Object result = null ;
???????????? if (
“close”.equals(
method.getName()?) {
?????????????????? cp.
releaseConnection( conn ) ;
} else {
?????? ??? ?result = method.invoke( obj , args )?;
}
?????????? Return result ;
????? }
}
有個類
ConnectionProxy后,我們只需要讓
DBConnectionPool
的方法
getConnection
返回動態代理即可。實現如下:
class DBConnectionPoolImpl implements DBConnectionPool {
??????????? public Connection getConnection() {
??????????????????????? Connection conn?;
??????????????????????? //
從池中取得連接或建立連接
??????????????????????? return (Connection)Proxy.newInstance(
????? ????????????? Connection.class.getClassLoader(),
??? ????????????????????? new Class[]{ Connection.class },
???????????? ?????? new
ConnectionProxy( conn )
?) ;
?
}
}
這樣就實現了連接池。
jdk1.5
提供的用于
rmi
的
dynamic stub
也使用
dynamic proxy
技術。只要你認真研究,其實很多問題都可以使用
dynamic proxy
來解決。