Dynamic Proxy介绍
一、
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
来解决。