【设计模式】动态代理Proxy_02

来源:互联网 发布:java 就业培训机构 编辑:程序博客网 时间:2024/05/20 16:00
我们继续上一次的动态代理探讨。

上一篇我们说道,所以我们要实现一种"通用"代理,可以对任意对象代理。
那么怎么实现呢?

我们规定产生代理的时候,被代理的类一定要实现一个接口。这样我们可以根据接口来生成代理对象,而不是根据具体的类。

我们明确一下我们的目标:我们可以对任何的对象,任何的类(前提是这个类实现了某个接口),我们就可以给它生成一个代理。

我们怎么样给它生成这个代理呢?为了模拟JDK的实现,我们添加一个新的类叫Proxy.java:

package cn.edu.hpu.proxy;public class Proxy {//这个方法用来产生新的代理类public static Object newProxyInstance(){return null;}}里面的newProxyInstance()方法用来产生新的代理类。我们假设他可以产生新的代理类,那么在Client类中执行的时候,就是这个样子的:package cn.edu.hpu.proxy;public class Client {public static void main(String[] args) {Moveable m=(Moveable)Proxy.newProxyInstance();m.move();}}

在这里,代理的类叫什么名字我们都不需要知道。

在我们将来做设计的时候都是站在使用者的角度去设计的,刚刚我们就先去模拟Proxy的使用场景,再回头去完善这个类。

现在我们要求对坦克进行代理,将来也可能对别的类进行代理,所有的代理都将由Proxy的newProxyInstance()方法来完成。动态代理的意义就是,我将不会再看到像TankTimeProxy和TankLogProxy等代理类的名字,而是在动态代理类中完成代理,然后产生一个被代理之后的类。

我们在Proxy类中引入TankTimeProxy的所有代码(这里为了简单,将所有stop()方法去除了),将它作为字符串,成为其成员变量src:
package cn.edu.hpu.proxy;public class Proxy {//这个方法用来产生新的代理类public static Object newProxyInstance(){String src="package cn.edu.hpu.proxy;"+"public class TankTimeProxy implements Moveable{"+"Moveable t;"+"long start;"+"long end;"+"public TankTimeProxy(Moveable t) {"+"super();"+"this.t = t;"+"}"+"public void before(){"+"start=System.currentTimeMillis();"+"System.out.println(\"开始时间:\"+start+\"ms\");"+"}"+"public void after(){"+"end=System.currentTimeMillis();"+"System.out.println(\"运行时间:\"+(end-start)+\"ms\");"+"}"+"@Override"+"public void move() {"+"this.before();"+"t.move();"+"this.after();"+"}"+"}";return null;}}

我们现在做这样一个假设,假设我们将src所存储的源码编译完,生成一个类,再把这个类load到内存,再由这个内存产生一个新的对象....想象一下,是不是这里面就是TankTimeProxy的代理吗。
如果我们真的能动态编译,那么我们就不再需要TankTimeProxy类了。我们只需要调用Proxy的newProxyInstance()方法,它会给我们动态编译一段代码,这段代码是实现了我们想要的代理的业务逻辑,比如说"时间"代理。实现完了之后,它会产生这个代理类的一个对象,然后反馈给我们。

可能大家有些绕,总结一下:动态代理怎么产生的呢?动态代理是你根本看不到那个代理类的名字,原来我们还要写一个TankTimeProxy,现在不用写,只要调用Proxy的newProxyInstance()方法,它会自动的返回一个具体的代理。而这个代理是Proxy内部生成一段代码,这段代码编译完之后生产一个具体对象,这段代码你想完成什么逻辑就完成什么逻辑,所以最后我们的难题就变成了,如果我们能动态编译这段代码的话,我们就能够生成这段代码的代理类,可以说我想在运行的时候生成什么类就生成什么类,我想怎么写就怎么写。

实现动态编译有N中方式,Jdk本身已经有动态编译的API(Complier API),也可以用一些网上现有的编译API,如CGLib,ASM,这些甚至不用调编译器来编译,它们会直接帮你生成编译好的二进制文件(因为Java的二进制编译规则在网上也是公开的)。

下面我们讲编译(注意编译API在JDK6及之后才有):
我们先简单的测试一下JDK1.6提供的Complier功能,我们平时编译都是在命令行中编译,输入javac来进行编译,我们下面在类中进行编译:
package cn.edu.hpu.ProxyTest;import java.io.File;import java.io.FileWriter;import javax.tools.JavaCompiler;import javax.tools.StandardJavaFileManager;import javax.tools.ToolProvider;import javax.tools.JavaCompiler.CompilationTask;public class Test1 {public static void main(String[] args) throws Exception{String rt="\r\n";String src="package cn.edu.hpu.proxy;"+ rt +"public class TankTimeProxy implements Moveable{"+ rt +"    Moveable t;"+ rt +"    long start;"+ rt +"    long end;"+ rt +"    public TankTimeProxy(Moveable t) {"+ rt +"        super();"+ rt +"        this.t = t;"+ rt +"    }"+ rt +"    public void before(){"+ rt +"        start=System.currentTimeMillis();"+ rt +"        System.out.println(\"开始时间:\"+start+\"ms\");"+ rt +"    }"+"    public void after(){"+ rt +"        end=System.currentTimeMillis();"+ rt +"        System.out.println(\"运行时间:\"+(end-start)+\"ms\");"+ rt +"    }"+ rt +"    @Override"+ rt +" public void move() {"+ rt +"        this.before();"+ rt +"        t.move();"+ rt +"        this.after();"+ rt +"    }"+ rt +"}";//拿到当前项目的根目录:System.getProperty("user.dir"));String fileName=System.getProperty("user.dir")+"/src/cn/edu/hpu/proxy/TankTimeProxy.java";//我们把src的源码写入自己创建的File文件中去File f=new File(fileName);FileWriter fw=new FileWriter(f);fw.write(src);fw.flush();fw.close();//编译(getSystemJavaCompiler()拿到系统默认的Java编译器,其实就是javac)JavaCompiler compiler=ToolProvider.getSystemJavaCompiler();//需要一个FileManager,用它来管理文件(参数1:诊断的监听器,参数2、3国际化)StandardJavaFileManager fileMgr=compiler.getStandardFileManager(null, null, null);//通过FileManager找到TankTimeProxy文件,然后放到一个Iterable中//Iterable是一个数组,用它可以进行迭代Iterable units=fileMgr.getJavaFileObjects(fileName);//参数:(输出位置,文件管理器对象,监听器,编译的时候指定的参数,用到那些class文件,需要编译哪些文件)CompilationTask t=compiler.getTask(null, fileMgr, null, null, null, units);//进行编译t.call();fileMgr.close();}}

我们运行main方法,之后在cn.edu.hpu.proxy包下生成了一个TankTimeProxy.Java类,然后我们查看Navigator视窗(表示工程在硬盘上的最原始情况),可以发现被编译好的class文件:

如图



编译好了,我们要把编译好的对象给load到内存中,因为我们的class文件在原始路径中,我么无法直接使用ClassLoad,要使用一个特殊的ClassLoad----URLClassLoader,在上面fileMgr.close();代码下继续添加下列代码:
//把编译好的.class文件加载到内存中//urls指定.class文件所放的地方(使用URL还可以Load从网上传过来的类)URL[] urls=new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src")};//ClassLoader指的是吧硬盘里的Java文件放到内存中的那些个类URLClassLoader ul=new URLClassLoader(urls);Class c=ul.loadClass("cn.edu.hpu.proxy.TankTimeProxy");System.out.println(c);

运行main方法,发现打印出了class cn.edu.hpu.proxy.TankTimeProxy,说明类已经成功Load到内存中了。

当然,Load进内存之后我们要生成它的一个对象,这个事情就要牵扯到反射了,我们下篇总结再说。

转载请注明出处:http://blog.csdn.net/acmman/article/details/46827967

0 0