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

Posted on 2010-08-16 10:55 java小爬虫 阅读(1625) 评论(0)  编辑  收藏
如上文:代理模式和装饰者模式中的静态代理实例,它具有如下缺陷:
    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(nullnullnull);
        Iterable units 
= fileMgr.getJavaFileObjects(fileName);
        CompilationTask compilationTask 
= compiler.getTask(null, fileMgr, nullnullnull, 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(nullnullnull);
        Iterable units 
= fileMgr.getJavaFileObjects(fileName);
        CompilationTask compilationTask 
= compiler.getTask(null, fileMgr, nullnullnull, 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 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,InvocationHandler h) 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 + t + 
                         
"public void " + method.getName() + "() {" + rt + t + t + t + 
                         
"try {" + rt + t + t + t + t +  
                         
"Method md = " + intface.getName() + ".class.getMethod(\"" + method.getName() + "\");" + rt + t + t + t + t +   
                         
"h.invoke(this, md);" + rt + t + t + t +
                         
"}catch(Exception e) {" + rt + t + t + t +      
                         
"e.printStackTrace();" + rt + t + t +     
                         
"}" + rt + t +                     
                         
"}" + rt + t + t ;
        }

        
        System.out.println(methodStr);
        
        String src 
="package proxy;"+ rt + t +  
        
"import "+intface.getName()+";"+ rt + t + 
        
"import java.lang.reflect.Method;"+ rt + t + 
        
"public class $Proxy1 implements "+intface.getSimpleName()+" {"+ rt + t +    
        
"private InvocationHandler h ;    "+ rt + t +    
        
"public $Proxy1(InvocationHandler h) {"+ rt + t +    
        
"super();"+ rt + t +    
        
"this.h = h;"+ 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(nullnullnull);
        Iterable units 
= fileMgr.getJavaFileObjects(fileName);
        CompilationTask compilationTask 
= compiler.getTask(null, fileMgr, nullnullnull, 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(InvocationHandler.class);
        Object m 
= ctr.newInstance(h);
        
return m;
    }

}


这样,就实现了对任意对象,任意方法的代理。
缺点就是:多接口代理没有实现,被代理对象的方法没有支持参数。

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


网站导航: