seaairland

 

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 来解决。

posted on 2006-03-24 13:34 chenhui 阅读(833) 评论(0)  编辑  收藏 所属分类: 设计模式


只有注册用户登录后才能发表评论。


网站导航:
 

导航

统计

常用链接

留言簿(1)

随笔分类

随笔档案

文章分类

文章档案

介绍 IOC

友情链接

最新随笔

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜