从静态代理到动态代理的演变

动态代理原理与实现
本文详细介绍了如何通过Java反射机制和编译API实现动态代理的过程,包括生成代理类、编译、加载及调用等步骤,并探讨了如何解决静态代理的局限性。

如上文:代理模式和装饰者模式中的静态代理实例,它具有如下缺陷:
    1:代理类不可重用,具有相同代理逻辑的类会大量产生;
    2:被代理方法惟一,如果有多个方法都需要相同逻辑的代理,那么代理类中就有大量的相似的方法存在;
    3:代理方法不具有参数;
    4:只实现了单接口了代理;
所以它并不具有实战意义上的价值!

那么,这些问题该如何解决呢?动态代理又是一步一步如何演变过来的呢?

下面就让我们来一步一步以实例的方式来探究它的演变的细节。

如果我们能动态产生一个代理类的源文件,编译后加载到内存,那么我们就可以获取到动态的代理对象。

package  proxy;

import  java.io.File;
import  java.io.FileWriter;
import  java.lang.reflect.Constructor;
import  java.net.URL;
import  java.net.URLClassLoader;

import  javax.tools.JavaCompiler;
import  javax.tools.StandardJavaFileManager;
import  javax.tools.ToolProvider;
import  javax.tools.JavaCompiler.CompilationTask;

public   class  Proxy {
    
public   static  Object newProxyInstance(Object target)  throws  Exception {  // JDK6 Complier API, CGLib, ASM

        String rt 
=   " \r\n " ;
        String t 
=   " \t " ;
            
        String src 
= " package proxy; " +  rt  +  t  +     
        
" public class TankTimeProxy implements Movable { " +  rt  +  t  +     
        
" private Movable obj;     " +  rt  +  t  +     
        
" public TankTimeProxy(Movable obj) { " +  rt  +  t  +     
        
" super(); " +  rt  +  t  +     
        
" this.obj = obj; " +  rt  +  t  +     
        
" } " +  rt  +  t  +     
        
" @Override " +  rt  +  t  +     
        
" public void move() { " +  rt  +  t  +     
        
" long begintime = System.currentTimeMillis(); " +  rt  +  t  +     
        
" System.out.println(\ "  Tank is begining to move  ! \ " ); " +  rt  +  t  +     
        
" obj.move(); " +  rt  +  t  +     
        
" long endtime = System.currentTimeMillis(); " +  rt  +  t  +     
        
" System.out.println(\ "  Tank is stop  ! \ " ); " +  rt  +  t  +     
        
" System.out.println(\ " move time : \ " +(endtime-begintime)); " +  rt  +  t  +     
        
" } " +  rt  +  t  +     
        
" } " ;  
        String fileName 
= System.getProperty( " user.dir " ) + " /src/proxy/TankTimeProxy.java " ;
        File f 
=   new  File(fileName);
        FileWriter fw 
=   new  FileWriter(f);
        
if (f.exists()){
            f.delete();
            fw.flush();
            f 
=   new  File(fileName);
            };
        fw.write(src);
        fw.flush();
        fw.close();
        
        
// compile
        JavaCompiler compiler  =  ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileMgr 
=  compiler.getStandardFileManager( null null null );
        Iterable units 
=  fileMgr.getJavaFileObjects(fileName);
        CompilationTask compilationTask 
=  compiler.getTask( null , fileMgr,  null null null , units);
        compilationTask.call();
        fileMgr.close();
        
        
// load into memory and create an instance
        URL[] urls  =   new  URL[] { new  URL( " file:/ "   +  System.getProperty( " user.dir " ) +   " /src/ " )};
        URLClassLoader ul 
=   new  URLClassLoader(urls);
        Class c 
=  ul.loadClass( " proxy.TankTimeProxy " );    
                
        Constructor ctr 
=  c.getConstructor(target.getClass().getInterfaces()[ 0 ]);
        Object m 
=  ctr.newInstance(target);
        
return  m;
    }
}


测试类:

package  proxy;
public   class  Client {
    
public   static   void  main(String[] args)  throws  Exception{
        Movable tank 
=  (Movable)Proxy.newProxyInstance( new  Tank());
        tank.move();
    }
}





上面的类就实现了生成TankTimeProxy.java文件,编译,加载并被调用的功能。(全部源码见: 代理模式和装饰者模式异同点比较

那么怎么实现代理任意对象呢?
在产生代理类的时候,只要动态的注入目标对象,就实现了对任意对象的代理。
怎么实现对任意方法的代理呢?通过java反射机制,可以获取一个类的所有方法,即可以获取目标类的所有方法,在组成代理类java源码的时候,循环遍历嵌入处理逻辑就可以任意对多方法的代理了。

示例代码如下:

package  proxy;

import  java.io.File;
import  java.io.FileWriter;
import  java.lang.reflect.Constructor;
import  java.lang.reflect.Method;
import  java.net.URL;
import  java.net.URLClassLoader;

import  javax.tools.JavaCompiler;
import  javax.tools.StandardJavaFileManager;
import  javax.tools.ToolProvider;
import  javax.tools.JavaCompiler.CompilationTask;

public   class  Proxy  {
    
public   static  Object newProxyInstance(Class intface,Object target)  throws  Exception  // JDK6 Complier API, CGLib, ASM

        String rt 
=   " \r\n " ;
        String t 
=   " \t " ;
        String methodStr 
=   ""  ;
                
        Method[] methods 
=  intface.getDeclaredMethods();
        
for  (Method method : methods)  {
            methodStr 
+=   " @Override " +  rt  +  t  +     
            
" public void  " + method.getName() + " () { " +  rt  +  t  +     
            
" long begintime = System.currentTimeMillis(); " +  rt  +  t  +     
            
" System.out.println(\ "  Tank is begining to move  ! \ " ); " +  rt  +  t  +     
            
" obj. " + method.getName() + " (); " +  rt  +  t  +     
            
" long endtime = System.currentTimeMillis(); " +  rt  +  t  +     
            
" System.out.println(\ "  Tank is stop  ! \ " ); " +  rt  +  t  +     
            
" System.out.println(\ " move time : \ " +(endtime-begintime)); " +  rt  +  t  +     
            
" } " +  rt  +  t; 
        }

        
        System.out.println(methodStr);
        
        String src 
= " package proxy; " +  rt  +  t  +   
        
" import  " + intface.getName() + " ; " +  rt  +  t  +  
        
" public class $Proxy1 implements  " + intface.getSimpleName() + "  { " +  rt  +  t  +     
        
" private  " + intface.getSimpleName() + "  obj;     " +  rt  +  t  +     
        
" public $Proxy1( " + intface.getSimpleName() + "  obj) { " +  rt  +  t  +     
        
" super(); " +  rt  +  t  +     
        
" this.obj = obj; " +  rt  +  t  +     
        
" } "   +  rt  +  t  +     
        methodStr 
+ rt  +
        
" } " ;
         
        String fileName 
= System.getProperty( " user.dir " ) + " /src/proxy/$Proxy1.java " ;
        File f 
=   new  File(fileName);
        FileWriter fw 
=   new  FileWriter(f);
        
if (f.exists()) {
            f.delete();
            fw.flush();
            f 
=   new  File(fileName);
            }
;
        fw.write(src);
        fw.flush();
        fw.close();
        
        
// compile
        JavaCompiler compiler  =  ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileMgr 
=  compiler.getStandardFileManager( null null null );
        Iterable units 
=  fileMgr.getJavaFileObjects(fileName);
        CompilationTask compilationTask 
=  compiler.getTask( null , fileMgr,  null null null , units);
        compilationTask.call();
        fileMgr.close();
        
        
// load into memory and create an instance
        URL[] urls  =   new  URL[]  { new  URL( " file:/ "   +  System.getProperty( " user.dir " ) +   " /src/ " )} ;
        URLClassLoader ul 
=   new  URLClassLoader(urls);
        Class c 
=  ul.loadClass( " proxy.$Proxy1 " );    
                                
        Constructor ctr 
=  c.getConstructor(intface);
        Object m 
=  ctr.newInstance(target);
        
return  m;
    }

}


但是新的问题又出现了:
在代理类中,被代理方法前后的处理逻辑已经被“写死了”,很难改变增加的功能,这又该如何处理呢?
我们可以这样考虑,增加一个调用处理器InvocationHandler,把对方法的处理逻辑进行进一步的封闭,并把InvocationHandler分离出来,如果可以的话,就实现了对代理逻辑的可修改性。
那么InvocationHandler里面应该封闭些什么东西呢?

for  (Method method : methods)  {
            methodStr 
+=   " @Override " +  rt  +  t  +     
            
" public void  " + method.getName() + " () { " +  rt  +  t  +     
            
" long begintime = System.currentTimeMillis(); " +  rt  +  t  +     
            
" System.out.println(\ "  Tank is begining to move  ! \ " ); " +  rt  +  t  +     
            
" obj. " + method.getName() + " (); " +  rt  +  t  +     
            
" long endtime = System.currentTimeMillis(); " +  rt  +  t  +     
            
" System.out.println(\ "  Tank is stop  ! \ " ); " +  rt  +  t  +     
            
" System.out.println(\ " move time : \ " +(endtime-begintime)); " +  rt  +  t  +     
            
" } " +  rt  +  t; 
        }

从这个片段代理可以看出,我们应该在InvocationHandler中封装obj对象,即被代理类的接口(实现类)。还应该实现:被分离出去的InvocationHandler能被代理类调用,我们应该把InvocationHandler聚合进来。

代码演变示例:

package  proxy;

import  java.lang.reflect.Method;

public   interface  InvocationHandler  {
    
public   void  invoke(Object proxy, Method m);
}

 

package  proxy;

import  java.lang.reflect.Method;

public   class  TimeHandler  implements  InvocationHandler {
    
    
private  Object target;

    
public  TimeHandler(Object target)  {
        
this .target  =  target;
    }


    @Override
    
public   void  invoke(Object o, Method m)  {
        
long  start  =  System.currentTimeMillis();
        System.out.println(
" starttime: "   +  start);
        System.out.println(o.getClass().getName());
        
try   {
            m.invoke(target);
        }
  catch  (Exception e)  {
            e.printStackTrace();
        }

        
long  end  =  System.currentTimeMillis();
        System.out.println(
" time: "   +  (end - start));
        System.out.println(
" endtime: "   +  end);
    }


}

 

package
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值