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

一个项目中有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中。



你可能感兴趣的:(AOP面向切面编程(二) 动态代理)