学习动态代理stepbystep(2)

学完了静态的,进行一个小小的进阶了。。。

这篇文章,是对java动态代理的一个小小的模拟。

有一个假设:被代理的类已经实现了一个接口[我们采用聚合方式]。(用继承也可以,但是不推荐)

模拟jdk的实现:

新建一个Proxy类,然后把TankTimeProxy.java文件中的内容,当作字符串复制到Proxy.java的一个方法中。

public class Proxy {
public static Object newProxyInstance(){

/* 

 *  动态代理,是解决类太多的问题。
*  Q:如果下面这个可以编译,则就可以不用TankTimeProxy类了

*  这样产生代理的总代理
*/
String str = 
"public class TankTimeProxy implements Moveable {" + 
"public TankTimeProxy(Moveable t){" +
"super();" +
"this.t = t;" +
"}" + 
"Moveable t;" +

"@Override" +
"public void move(){" +
"long start = System.currentTimeMillis();" +
"t.move();" +     //在生成的文件中move方法会调用tank的,因为在Proxy.java中存在着Object o = (Object)ctr.newInstance(new Tank());
"long end = System.currentTimeMillis();" +
"System.out.println(\"time: \" + (end - start));" +
"}" +
"}";
return str;
}
}


/* 实现动态编译,有许多种方法
 *    1.jdk6 的compilier
 *  2.cglib
 *  3.asm
 *  后两者不需要编译了,直接生成2进制文件。
 *  如果是接口,采用第一种机制
 * */

现在,已经产生了一个X.java文件所需要的代码(字符串),我们需要对它进行的处理是: 1.生成.java文件  2.编译成.class 3.加载到内存中.4.生成真正的对象,这样,就可以供我们使用了。

一步一步来吧:

先放在main里面,这样方便对每一步即时测试嘛:

public class Proxy {
public static void main(String[] args) throws Exception{
String rt = "\r\n";
String str = 
"public class TankTimeProxy implements Moveable {" + rt + 
"\tpublic TankTimeProxy(Moveable t){" + rt + 
"\tsuper();" + rt +
"\tthis.t = t;" + rt +
"\t}" +  rt +
"\tMoveable t;" + rt +

"\t@Override" + rt +
"\tpublic void move(){" + rt +
"\t\tlong start = System.currentTimeMillis();" + rt +
"\t\tt.move();" + rt +
"\t\tlong end = System.currentTimeMillis();" + rt +
"\t\tSystem.out.println(\"time: \" + (end - start));" + rt +
"\t}" + rt +
"}";


1.先生成.java文件:

//  user.dir:当前路径。如我的是:C:\Users\Seven\Workspaces\MyEclipse 8.6\Proxy

String fileName = System.getProperty("user.dir") + "/src/TankTimeProxy.java";
File f  = new File(fileName);
FileWriter fw = new FileWriter(f);
fw.write(str);
fw.flush();
fw.close();

(plus:后来的实践发现,当执行完这个程序的时候 ,MyEclipse已经在它自动生成class文件夹的目录下,生成了这个.class文件。)

2.对.java文件进行编译,生成.class文件:

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

/*注意:让上面这句话编译通过可能需要调IDE的JRE,我用的M8.6,可以不调
System.out.println(compiler.getClass().getName());
输出为:com.sun.tools.javac.api.JavacTool
*/
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable units = fileManager.getJavaFileObjects(fileName);
CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);
t.call();
fileManager.close();


/*
* 此时,就生成.class文件了
* 在Navigator视图下,可以看到已经编译完成的.class文件,这个视图真实地反应了硬盘上的存储情况。
*但这样还不够,还要把它load到内存,再生成对象:用到了反射。
*load into memory and create instance
*classloader:必须保证class在classpath(bin)下面,但现在是在源码文件夹下。
*/

3.加载至内存

///哎,这里有个陷阱,"/src"的写法是不能变为它的目录的(原来找class文件与找java文件是不一样滴)

///只能在下面改:Class c = ul.loadClass("com.compiler.test.TankTimeProxy");  

URL[] uris = new URL[] {new URL("file:/" + System.getProperty("user.dir") + "/src")};
URLClassLoader  ul = new URLClassLoader(uris);
Class c = ul.loadClass("TankTimeProxy");  //此处的名字应该与上面遥相呼应(这样就是放在了根目录下的情况)
System.out.println(c);


4.生成真正的对象

/*通过上面的操作,已经把我们需要的类load到内存了,但是还要生成对象
其实也可以直接生成到bin文件夹下,只是学会一种随意load的方法
在实际中,可以动态生成str,即java代码

下一步:生成对象。
通常:c.newInstance();生成一个对象,但是,这是生成构造函数为空的对象,而我们都是有带参数的。
所以得换其他的方法。
*/

//这句话是说,我要建立一个新的构造方法,而这个构造方法的参数,要是Movealbe类型的
Constructor ctr = c.getConstructor(Moveable.class);         //拿到构造方法
Moveable m = (Moveable)ctr.newInstance(new Tank());   //这样就会调用含参的构造方法,传入参数,正式生成对象
m.move();

///可以随意改变类名:TankTimeProxy.java
/*然后,删除掉.java文件,这样,就可以了。*/





阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭