本文可作为北京尚学堂 设计模式的学习笔记
在上一节 我们用谈到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有关文件的知识点 )
第二步
//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文件
其实说到这里 大家会觉得第二步都是些什么呀? 全部都是这个咱们不管 这个暂时不理会
说实话 对第二步 我也没有仔细的研究过 能用即可 我认为 这部分的相关知识 要想研究 问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的实现类
别的接口 我们暂时还没有办法
只是 大家想想 我们要继续实现动态代理的话 要改的其实也就是那个字符串
大家还怕什么?
剩下的内容 我们在下一篇文章里聊