首页 > 代码库 > 继承?静态代理?写一个自己的动态代理吧

继承?静态代理?写一个自己的动态代理吧

[ 需求分析 ]

在我们实际开发中常常会遇到这样的问题:记录一个类的方法运行时间,以分析性能。一般我们的做法是先在类的开始记录一个开始时间,然后在类的结束记录一个结束时间,二者相减就可以获取我们想要的结果。但是很多时候这些类已经打了jar包,我们无法直接修改源码,这个时候我们应该怎么办呢?

下文使用Tank的移动需要统计时间、记录日志来模拟需求场景,假定Moveable、Tank类无法修改。


interface:Moveable

public interface Moveable {
	public void move();
}


realization:Tank

public class Tank implements Moveable{
	public void move() {
		System.out.println("tank is moving");
	}
}

[ 继承实现 ]

① 现在我们假设需要统计Tank移动的时间,通过实现Tank,并加入统计时间的代码即可做到

import java.util.Random;
/**
 * use to record tank move time
 * @author zhangjim
 */
public class TankTimeProxy extends Tank {
	public void move() {
		try {
			long start = System.currentTimeMillis();
			super.move();
			// make the tank move solwly
			Thread.sleep(new Random().nextInt(1000)); 
			long end = System.currentTimeMillis();
			System.out.println("total spend time: " + (end - start) + "ms");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
② 测试代码:

/**
 * test client
 * @author zhangjim
 */
public class Client {
	public static void main(String[] args) {
		Moveable m = new TankTimeProxy();
		m.move();
	}
}
③ 分析:

上述方法可以解决我们的问题,但如果现在需要增加需求:加入日志记录功能,那么我们就要再继承TankTimeProxy

/**
 * use to record tank move logs
 * @author zhangjim
 */
public class TankLogProxy extends TankTimeProxy{
	public void move() {
		System.out.println("tank start to move...");
		super.move();
		System.out.println("tank stop to move...");
	}
}

这种实现方法存在一个很大的缺陷:很不灵活,如果后期还要加功能的话,就要不断的继承下去,父子关系过于臃肿,不利于维护。

[ 静态代理 ]

① 接上文,现在我们使用静态代理实现上面的功能:

重写TankTimeProxy:

import java.util.Random;

/**
 * use to record tank move time
 * @author zhangjim
 */
public class TankTimeProxy implements Moveable {
	private Moveable m;

	public TankTimeProxy(Moveable m) {
		this.m = m;
	}

	@Override
	public void move() {
		try {
			long start = System.currentTimeMillis();
			m.move();
			Thread.sleep(new Random().nextInt(1000));
			long end = System.currentTimeMillis();
			System.out.println("total spend time: " + (end - start) + "ms");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}


重写TankLogProxy:
/**
 * use to record tank move logs
 * @author zhangjim
 */
public class TankLogProxy implements Moveable{
	private Moveable m;

	public TankLogProxy(Moveable m) {
		this.m = m;
	}

	@Override
	public void move() {
		System.out.println("tank start to move...");
		m.move();
		System.out.println("tank stop to move...");
	}
}

② 测试一下吧

/**
 * test client
 * @author zhangjim
 */
public class Client {
	public static void main(String[] args) {
		Moveable m = new TankLogProxy(new TankTimeProxy(new Tank()));
		m.move();
	}
}

③ 分析:

通过代码不难看出,代理类和被代理类实现了同一个接口,其实这个就是装饰设计模式。相对于继承,聚合的类结构无疑更方便管理和维护,但是它仍然有一个弊病:对于不同的功能,我仍然需要加类。例如:加权限控制功能需要TankAuthorityProxy,加事务控制功能需要TankTransactionProxy等等,能不能让计算机帮我们产生这些类?自己动手试试吧!


[ 我的动态代理 ]

① 所谓的动态代理,就是说上文的TankTimeProxyTankLogProxy不需要我们手动创建了计算机会帮我们动态生成,也就是说这个代理类不是提前写好的,而是程序运行时动态生成的。


我们写一个proxy类,它的作用就是帮我们产生代理类。

主要实现思路:

i  将所有方法代码拼接成字符串

ii 将生成代理类的代码拼接成字符串(包含所有方法拼接成的字符串)

iii 将此字符串写入文件中、并使用JavaComplier对它进行编译

Ⅳ 将编译好的文件load进内存供我们使用,并返回代理实例。

public class Proxy {
	public static Object newProxyInstance(Class intefc, InvocationHandler handle) throws Exception {
		String rt = "\r\t" ;
		String methodStr = "" ;
		
		// first we should realize all the methods of the interface
		Method[] methods = intefc.getMethods();
		for (Method m : methods) {
			methodStr +="public void "+m.getName()+"(){"+rt+
						"    try{"+rt+
						"        Method method = "+intefc.getName()+".class.getMethod(\""+m.getName()+"\");" + rt +
						"        handle.invoke(this,method);" +rt+
						"    }catch(Exception ex){}" +rt+
						"}" ;
						
		}
		String clazzStr =  "package com.zdp.dynamicProxy;"+rt+
						   "import java.lang.reflect.Method;"+rt+
						   "public class $Proxy1 implements "+intefc.getName()+"{"+rt+
						   "    private com.zdp.dynamicProxy.InvocationHandler handle ;"+rt+
						   "    public $Proxy1(InvocationHandler handle){"+rt+
						   "        this.handle=handle;"+rt+
						   "    }"+rt+
						   "    @Override"+rt+
						    methodStr +rt+
						   "}";
		
		
		// write to a java file 
		File file = new File("D:/develop_environment/babasport/homework/src/com/zdp/dynamicProxy/$Proxy1.java") ;
		FileWriter writer = null ;
		try {
			writer = new FileWriter(file);
			writer.write(clazzStr) ;
			writer.flush() ;
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				if(writer !=null){
					writer.close() ;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		//load the java file, and then create an instance 
		URL[] urls = new URL[] {new URL("file:/" + "D:/develop_environment/babasport/homework/src/")};
		URLClassLoader urlLoader = new URLClassLoader(urls);
		Class c = urlLoader.loadClass("com.zdp.dynamicProxy.$Proxy1");
		
		//return the proxy instance
		Constructor ctr = c.getConstructor(InvocationHandler.class);
		Object proxyInstance = ctr.newInstance(handle);

		return proxyInstance;
	}
}
② 因为要处理所有的业务,我们定义一个接口InvocationHandler

public interface InvocationHandler {
	public void invoke(Object o, Method m) ;  
}

③ MyHandler是真正的处理类

/**
 * use to record logs and time
 * @author zhangjim
 */
public class MyHandler implements com.zdp.dynamicProxy.InvocationHandler {
	private Object target;

	public MyHandler(Object target) {
		this.target = target;
	}

	public void invoke(Object obj, Method method) {
		try {
			System.out.println("tank start to move...");
			long start = System.currentTimeMillis();
			method.invoke(target);
			Thread.sleep(new Random().nextInt(1000));
			long end = System.currentTimeMillis();
			System.out.println("total spend time: " + (end - start)  + "ms");
			System.out.println("tank stop to move...");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
④ Proxy类会帮我们动态创建一个类$Proxy1

public class $Proxy1 implements com.zdp.dynamicProxy.Moveable {
	private com.zdp.dynamicProxy.InvocationHandler handle;

	public $Proxy1(InvocationHandler handle) {
		this.handle = handle;
	}

	@Override
	public void move() {
		try {
			Method method = com.zdp.dynamicProxy.Moveable.class.getMethod("move");
			handle.invoke(this, method);
		} catch (Exception ex) {
		}
	}
}

⑤ 测试一下,看看效果怎么样

/**
 * test client
 * @author zhangjim
 */
public class Client {
	public static void main(String[] args) throws Exception {
		Moveable m = new Tank();
		InvocationHandler handle = new MyHandler(m);
		Moveable proxy = (Moveable) Proxy.newProxyInstance(Moveable.class, handle);
		proxy.move();
	}
}

⑥ 简要总结:

Proxy:动态创建代理类,通过调用处理器的处理方法来实现代理。

InvocationHandler:实现对被代理对象的处理。


[ 应用场景 ]

① 系统日志记录

② 权限控制(符合一定条件才执行某方法)

③ 事务控制(方法执行之前开启事务,方法执行之后提交或回滚事务)


[ 特别感谢 ]

这篇日志是对马士兵老师:《设计模式之动态代理》的一个总结,非常感谢马老师的辛勤工作。