您的位置:首页 > 其它

动态代理技术

2006-03-21 16:16 344 查看
[align=center]Dynamic Proxy介绍[/align]
一、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来解决。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: