从坦克聊聊代理模式之动态代理1

本文可作为北京尚学堂 设计模式的学习笔记


在上一节 我们用谈到TankTimeProxy已经写死了 只能为一个类型的接口服务
在这一节 我们就试试解决这个问题 让代理类可以为任何类服务

package proxy;

public class Proxy {
	public static Object newInstance(){
		return null;
	}

}
我们假定有这个一个类 Proxy 它可以生成我们需要的代理类
那么相应的Client类就应该如下 可见最关键的部分就是这个newInstance是如何工作的

package proxy;

public class Client {
	public static void main(String[] args) {		
		Imoveable m=(Imoveable) Proxy.newInstance();
		t.move();
	}
}
为了方便 我们暂时不动Proxy 先写一个测试类

package proxy.compiler.test;

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

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

import proxy.Imoveable;
import proxy.Tank;

public class Test {
	public static void main(String[] args)  {

		String rt = "\r\n";
		String src = "package proxy.compiler.test;" + rt
				+ "import proxy.Imoveable;" + rt
				+ "public class TankTimeProxy implements Imoveable {" + rt
				+ "    public TankTimeProxy(Imoveable t) {" + rt
				+ "        super();" + rt + "        this.t = t;" + rt
				+ "    }" + rt +

				"    Imoveable t;" + rt +

				"    @Override" + rt + "    public void move() {" + rt
				+ "        long start = System.currentTimeMillis();" + rt
				+ "        System.out.println(\"starttime:\" + start);" + rt
				+ "        t.move();" + rt
				+ "        long end = System.currentTimeMillis();" + rt
				+ "        System.out.println(\"end time:\" + end);" + rt
				+ "        System.out.println(\"expend time:\" + (end-start));" + rt
				+ "    }" + rt + "}";
	
	}

}
对 我们把TankTimeProxy变成了一个字符串 再下面其实就是三步
第一 把字符串写进硬盘 变成一个java类
第二 我们要编译java类 生成class文件
第三 加载class文件

具体代码如下
第一步

                String fileName = System.getProperty("user.dir")
				+ "/src/proxy/compiler/test/TankTimeProxy.java";

		File f = new File(fileName);
		FileWriter fw = new FileWriter(f);
		fw.write(src);
		fw.flush();
		fw.close();
此时在Eclipse左边的资源栏里可以看到 新生成了一个TankTimeProxy类
System.getProperty("user.dir") 取得的是项目在硬盘上的目录
(上面的代码如果不清楚 可以看看java有关文件的知识点 )

从坦克聊聊代理模式之动态代理1_第1张图片

第二步
        

   //compoler
        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = javaCompiler
                .getStandardFileManager(null, null, null);
        Iterable units = fileManager.getJavaFileObjects(fileName);
        CompilationTask task = javaCompiler.getTask(null, fileManager, null,
                null, null, units);
        task.call();
        fileManager.close();
这一步不懂得人就有很多了
先一点来说
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
看名字都知道 我们要取得编译器 这里要说明一点
如果javacompiler为null 那么问题在于Eclipse设置的vm为jre  jre里面是不包含编译命令的 改成jdk即可
JavaCompiler里的相关方法的说明如下
    StandardJavaFileManager getStandardFileManager(
        DiagnosticListener<? super JavaFileObject> diagnosticListener,
        Locale locale,
        Charset charset);
这个DiagnosticListener 是出错后的监听类 咱们暂时不理会
后面两个参数 都是和国际化 字符集有关 我们也可以不管 三个参数都为null

 CompilationTask getTask(Writer out,
                            JavaFileManager fileManager,
                            DiagnosticListener<? super JavaFileObject> diagnosticListener,
                            Iterable<String> options,
                            Iterable<String> classes,
                            Iterable<? extends JavaFileObject> compilationUnits);
这里面的参数 也很多 但我们还是不用管 把前面的两个变量赋进去 即可
再后面就是调用 关闭fileManager
看navigator 我们可以看到多了一个class文件

从坦克聊聊代理模式之动态代理1_第2张图片

其实说到这里 大家会觉得第二步都是些什么呀? 全部都是这个咱们不管 这个暂时不理会
说实话 对第二步 我也没有仔细的研究过 能用即可 我认为 这部分的相关知识 要想研究 问Google
不过我真的不认为 这些略过的东西很重要


继续看第三步
到这里 class文件我们也有了
再下面就是利用反射的知识获得类

        // 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.compiler.test.TankTimeProxy");
        
        Constructor cons= c.getConstructor(Imoveable.class);
        Imoveable m=(Imoveable)cons.newInstance(new Tank());
        m.move();
我们用UrlClassLoader从src底下加载进来类
这里有两点要考虑
第一 用UrlClassLoader 也可以直接load网络上的类
第二 我们的class文件并没有放在bin目录下 是为了怕与下面讲的jdk里面的的代理产生的类相混淆 其实放在哪里都行

得到class c后如果c本身有无参的构造函数 直接调用newinstance即可
这里我们看到TankTimeProxy本身没有无参的构造函数 所以我们只能取出它的构造函数 用构造函数来生成类

话说我自己在写这部分代码的时候 心里还有一个疑问
c.getConstructor(Imoveable.class); 如果类里面有多个方法 它的参数都是Imoveable 怎么办?
呵呵 见笑了

仔细看看上面三步 然后将test里面的内容拷贝到Proxy的newinstance方法里
如下

package proxy;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
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 newInstance() throws IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
		String rt = "\r\n";
		String src = "package proxy.compiler.test;" + rt
				+ "import proxy.Imoveable;" + rt
				+ "public class TankTimeProxy implements Imoveable {" + rt
				+ "    public TankTimeProxy(Imoveable t) {" + rt
				+ "        super();" + rt + "        this.t = t;" + rt
				+ "    }" + rt +

				"    Imoveable t;" + rt +

				"    @Override" + rt + "    public void move() {" + rt
				+ "        long start = System.currentTimeMillis();" + rt
				+ "        System.out.println(\"starttime:\" + start);" + rt
				+ "        t.move();" + rt
				+ "        long end = System.currentTimeMillis();" + rt
				+ "        System.out.println(\"end time:\" + end);" + rt
				+ "        System.out.println(\"expend time:\" + (end-start));" + rt
				+ "    }" + rt + "}";
		String fileName = System.getProperty("user.dir")
				+ "/src/proxy/compiler/test/TankTimeProxy.java";

		File f = new File(fileName);
		FileWriter fw = new FileWriter(f);
		fw.write(src);
		fw.flush();
		fw.close();

		//compoler
		JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileManager = javaCompiler
				.getStandardFileManager(null, null, null);
		Iterable units = fileManager.getJavaFileObjects(fileName);
		CompilationTask task = javaCompiler.getTask(null, fileManager, null,
				null, null, units);
		task.call();
		fileManager.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.compiler.test.TankTimeProxy");

		
		Constructor cons= c.getConstructor(Imoveable.class);
		Imoveable m=(Imoveable)cons.newInstance(new Tank());
		return m;
	}

}

相应的我们的Client也会变成如下

package proxy;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

public class Client {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {

        Imoveable m=(Imoveable) Proxy.newInstance();
        m.move();
    }
}


坦克的move方法做了修改
    @Override
    public void move() {
        // TODO Auto-generated method stub
        System.out.println("i can move...");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


其实也就是让程序随机休眠一会
运行结果
starttime:1406021695484
i can move...
end time:1406021697031
expend time:1547

其实讲到这里 我们也这是完成了动态代理一半的工作 因为现在我们仍然只能代理Imoveable的实现类
别的接口 我们暂时还没有办法
只是 大家想想 我们要继续实现动态代理的话 要改的其实也就是那个字符串
大家还怕什么?
剩下的内容 我们在下一篇文章里聊


你可能感兴趣的:(设计模式,jdk,compiler,编译器)