动态代理技术

Dynamic Proxy介绍
一、proxy模式简介
GoF介绍了proxy模式。代理对象为其他对象提供一种代理以控制对这个对象的访问。它静态结构如下:
Client需要访问RealSubject时,它实际访问的是Proxy对象,而后Proxy对象将请求委托给RealSubjectRealSubject实现了主要的逻辑,Proxy对象可以在处理请求之前、之后作额外的处理。可以看出,ProxyRealSubject实现了同样的接口,这样Client才可以调用RealSubject实现的所有Subject的方法。
我们在实现Proxy时,如果使用的是C++语言,我们可以重载操作符->来实现代理。优点是实现简单,缺点是它不能区别对待不同的请求。当然也可以是用普通的形式,创建一个代理类,实现接口,并将调用委托给被代理的对象。
如果使用的是Java语言,我们当然可以使用普通的形式来实现Proxy模式。但是JDK1.3引入了dynamic proxy,它允许我们更容易的实现代理。
二、JDK中的Dynamic Proxy介绍
它由java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler等组成。Proxy类拥有一个protectedInvocationHandler类型的成员变量。它只能代理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提供的用于rmidynamic stub也使用dynamic proxy技术。只要你认真研究,其实很多问题都可以使用dynamic proxy来解决。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值