读书人

AOP面临切面编程(二) 动态代理

发布时间: 2013-01-28 11:49:56 作者: rapoo

AOP面向切面编程(二) 动态代理

一个项目中有10个类包括Tank, Car, Truck等,这些类都实现了Moveable,并且有Move方法。

public interface Moveable {void move();}public class Tank implements Moveable {@Overridepublic void move() {System.out.println("Tank Moving...");}}public class Car implements Moveable {@Overridepublic void move() {System.out.println("Car Moving...");}}

现在的业务需求是,要求在调用这些类的move方法的时候,要记录下每次move方法执行的时间。当然,最简单的方法是改动代码。

public class Tank implements Moveable {@Overridepublic 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 {@Overridepublic 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中。



读书人网 >编程

热点推荐