一个项目中有10个类包括Tank, Car, Truck等,这些类都实现了Moveable,并且有Move方法。
public interface Moveable { void move(); } public class Tank implements Moveable { @Override public void move() { System.out.println("Tank Moving..."); } } public class Car implements Moveable { @Override public void move() { System.out.println("Car Moving..."); } }
现在的业务需求是,要求在调用这些类的move方法的时候,要记录下每次move方法执行的时间。当然,最简单的方法是改动代码。
public class Tank implements Moveable { @Override public void move() { long start = System.currentTimeMillis(); System.out.println("Tank Moving..."); long end = System.currentTimeMillis(); System.out.println(\"time:\" + (end-start)); } }
现在因为要学习动态代理,我们不去改动代码,而另外想办法来完成这个功能。或许我们这样想,我们接到的是别人的项目,只有文档而没有代码,当我们在使用这些Car,Tank类的时候,就没有办法改代码了。有人说,此时可以通过继承或者组合的方式来实现这个
public class MyMove implements Moveable{ Private Moveable m; Public void MyMove(Moveable m) { This.m = m; } public void move() { long start = System.currentTimeMillis(); m.move(); long end = System.currentTimeMillis(); System.out.println("time:" + (end-start)); } }
当在使用Tank的时候,Moveable tank = new MyMove(new Tank());
Tank.move()的时候,就能记录时间了。
当使用Car的时候,Moveable car= new MyMove(new Car());
car.move();也实现了记录时间。
但是这样做很有局限性,此时的业务需求如果变化了,要求不仅要记录move的时间,还要记录何时开始,何时结束。还要改代码,如果业务有变化了,就又要改代码。那么我现在想寻求一种办法,做一个工具,只需要把要改的逻辑以参数的方式传进去,不论以后业务如何变化,只需要把相应的逻辑传进去就行,记录时间或者其他都没问题。
为了生成这个工具,我们一步步实现。
首先我们要完成的任务是,把一个字符串编译成一个类并且进行使用。此时看来跟目标需求可能没有关心,不要着急,这是基础。
public class MyProxy { private Moveable vehicle; public void setVvehicle(Moveable vehicle) { this.vehicle = vehicle; } public Object getInstance() { String rt = "\r\n"; String src = "public class TimeProxy implements Moveable {" + rt + " Moveable t;" + rt + " public void setT(Moveable t){ " this.t = t; " } " @Override" + rt + " public void move() {" + rt + " long start = System.currentTimeMillis();" + rt + " t.move();" + rt + " long end = System.currentTimeMillis();" + rt + " System.out.println(\"time:\" + (end-start));" + rt + " }" + rt + "}"; String fileName = System.getProperty("user.dir") + "/src/proxy/TimeProxy.java"; File f = new File(fileName); FileWriter fw = new FileWriter(f); fw.write(src); fw.flush(); fw.close(); // 上边这段代码的作用是,根据已知的字符串,通过IO操作生成.java文件放到硬盘中 //compile 下边这段代码的作用是,把刚刚生成的.java文件编译成.class文件 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); Iterable units = fileMgr.getJavaFileObjects(fileName); CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units); t.call(); fileMgr.close(); //load into memory and create an instance 下边这段代码的作用是,把生成的.class文件加载到内存中使用。对于这不理解可以看看我相应的博客讲反射 URL[] urls = new URL[] {new URL("file:/" + System.getProperty("user.dir") +"/src")}; URLClassLoader ul = new URLClassLoader(urls); Class c = ul.loadClass("proxy.TimeProxy"); Constructor ctr = c.getConstructor(Moveable.class); //获取相应的构造方法,这个构造方法是以Moveable m为参数的 Moveable m = (Moveable)ctr.newInstance(vehicle); return m; } }
在使用的时候,可以这样
public void test() { MyProxy myProxy = new MyProxy(); myProxy.vehicle = new Tank(); Moveable newTank = (Moveable)myProxy.getInstance(); newTank.move(); }
这次我们不把字符串采用硬编码的方式,这次,我们以传入接口作为参数的方式,来组装字符串。
public class MyProxy { private Class theInterface; public MyProxy(Class theInterface) { this.theInterface = theInterface; } private Object theProxyedInstance; //传入被代理的对象,也就是说,在MyProxy在使用的时候,要给其传入两个参数,一个要被代理的对象实现的接口,另一个是一个被代理对象的实例对象 public void setTheProxyedInstance(Object theProxyedInstance) { this.theProxyedInstance = theProxyedInstance; } public Object getInstance() throws Exception { Method[] methods = theInterface.getMethods(); String rt = "\r\n"; String methodStr = ""; for(Method m : methods) { methodStr += "@Override" + rt + "public void " + m.getName() + "() {" + rt + " long start = System.currentTimeMillis();" + rt + " i." + m.getName() + "();" + rt + " long end = System.currentTimeMillis();" + rt + " System.out.println(\"time:\" + (end-start));" + rt + "}"; } String src = "package test;"+ rt + "public class TimeProxy implements " + theInterface.getName() + "{" + rt + " private " + this.theInterface.getName() + " i; " + rt + " public TimeProxy(" + this.theInterface.getName() + " i) {" + rt + " this.i = i; " + rt + " }" + rt + methodStr + "}"; String fileName = System.getProperty("user.dir") + "/src/test/TimeProxy.java"; File f = new File(fileName); FileWriter fw = new FileWriter(f); fw.write(src); fw.flush(); fw.close(); // 上边这段代码的作用是,根据已知的字符串,通过IO操作生成.java文件放到硬盘中 //compile 下边这段代码的作用是,把刚刚生成的.java文件编译成.class文件 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); Iterable units = fileMgr.getJavaFileObjects(fileName); CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units); t.call(); fileMgr.close(); //load into memory and create an instance 下边这段代码的作用是,把生成的.class文件加载到内存中使用。对于这不理解可以看看我相应的博客讲反射 URL[] urls = new URL[] {new URL("file:/" + System.getProperty("user.dir") +"/src/")}; URLClassLoader ul = new URLClassLoader(urls); Class c = ul.loadClass("test.TimeProxy"); Constructor ctr = c.getConstructor(this.theInterface); //获取相应的构造方法,这个构造方法是以Moveable m为参数的 return ctr.newInstance(theProxyedInstance); }
现在来讲的话,只需要给MyProxy传入某一个接口以及实现这个接口的对象实例,MyProxy就能产生一个能够加入时间记录逻辑的相应的实例
另外的接口用于测试
package test; public interface MyInterface { public void interfaceTest(); } package test; public class T implements test.MyInterface { @Override public void interfaceTest() { System.out.println("aaaaaaa"); } } package test; public class Test1 { public static void main(String[] args) throws Exception{ MyProxy m = new MyProxy(MyInterface.class); m.setTheProxyedInstance(new T()); MyInterface myInterface = (MyInterface)m.getInstance(); myInterface.interfaceTest(); } }
到这里的话,我们就有了一个工具类MyProxy,能够完成对所有的实现了接口的实体对象的代理。
接下来要做的是,要把扩展逻辑也要做的灵活,就像前边讲的,此时的业务需求如果变化了,要求不仅要记录move的时间,还要记录何时开始,何时结束。这时,我们需要把扩展逻辑也要作为一个参数传入到这个MyProxy中。