终于把动态代理的视频看完了。那视频长的可谓“浩浩汤汤,横无际涯”。不过马士兵老师将的还不错。很多细节问题可以先不去深究,先来看看脉络。
所谓动态代理,即DynamicProxy。现在有一个接口Moveable,里面有个move方法,任何可移动的物体都可以继承它。
public interface Moveable { void move(); }
Tank类实现了moveable接口,并且有它自己的move逻辑。
public class Tank implements Moveable { @Override public void move() { System.out.println("Tank Moving..."); try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } }
现在我想在这个方法上判断一下这个方法运行了多长时间,或者做个日志之类的。最简单的方法就是直接在方法体上加代码,但是对于某些源码不可见,就不能加上自己的代码了。这时候可以用继承或者聚合。继承就是写一个TimeProxy继承Tank,在Tank的move方法前后加上些逻辑;聚合就是在TimeProxy中添加成员变量Tank。显然聚合更好,因为:想再move方法上想记录日志,判断权限,事务控制等等功能上的叠加,如先记录日志再记录时间。用继承的话要再加一个日志,要是先记录时间再记录日志呢?那又得再写一个类。而用聚合的话,各个Proxy都实现moveable,里面有个Tank成员,实现功能上的叠加就简单多了。。
Tank t = new Tank(); TankTimeProxy ttp = new TankTimeProxy(t); TankLogProxy tlp = new TankLogProxy(ttp);
这时候简单的静态代理就实现了。
如果相对任意对象的任意方法调用任何功能。前提:被代理的对象都实现了某个接口A。
首先有个一Proxy类,里面有一个newProxyInstance方法,它传入参数为被代理对象实现的接口A,处理逻辑(如添加日志)对象H。在newProxyInstance方法里面,它获取接口A的所有方法(反射),动态的生成一段代码(日志代理类,这个过程最难,但细节问题不去深究~),实现接口A,并且在方法体内,调用H的invoke方法(见下面代码),传入要做代理的那个方法。里面先实现日志,再进行方法调用(反射)。这样,以后任意多个实现同一接口的对象想做日志、时间、事物,Proxy和InvocationHandler都不必再更改,只需自己再写一个实现类实现InvocationHandler,里面写上自己的逻辑。
public interface InvocationHandler { public void invoke(Object o, Method m); }
public class TimeHandler implements InvocationHandler{ private Object target; //被代理对象 public TimeHandler(Object target) { super(); this.target = target; } @Override public void invoke(Object o, Method m) { //代理类对象,被代理对象调用的方法 long start = System.currentTimeMillis(); System.out.println("starttime:" + start); System.out.println(o.getClass().getName()); try { m.invoke(target); } catch (Exception e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("time:" + (end-start)); } }
最后调用:
Tank t = new Tank(); InvocationHandler h = new TimeHandler(t); Moveable m = (Moveable)Proxy.newProxyInstance(Moveable.class, h);//m就是一个代理 m.move();
附:Proxy的具体实现(不重要)
package com.bjsxt.proxy; import java.io.File; import java.io.FileWriter; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import javax.tools.JavaCompiler; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import javax.tools.JavaCompiler.CompilationTask; public class Proxy { public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception { //JDK6 Complier API, CGLib, ASM String methodStr = ""; String rt = "\r\n"; Method[] methods = infce.getMethods(); for(Method m : methods) { methodStr += "@Override" + rt + "public void " + m.getName() + "() {" + rt + " try {" + rt + " Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt + " h.invoke(this, md);" + rt + " }catch(Exception e) {e.printStackTrace();}" + rt + " }"; } String src = "package com.bjsxt.proxy;" + rt + "import java.lang.reflect.Method;" + rt + "public class $Proxy1 implements " + infce.getName() + "{" + rt + " public $Proxy1(InvocationHandler h) {" + rt + " this.h = h;" + rt + " }" + rt + " com.bjsxt.proxy.InvocationHandler h;" + rt + methodStr + rt + "}"; String fileName = "d:/src/com/bjsxt/proxy/$Proxy1.java"; File f = new File(fileName); FileWriter fw = new FileWriter(f); fw.write(src); fw.flush(); fw.close(); //compile JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); //JDK1.6 编译API 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 URL[] urls = new URL[] {new URL("file:/" + "d:/src/")}; URLClassLoader ul = new URLClassLoader(urls); Class c = ul.loadClass("com.bjsxt.proxy.$Proxy1"); System.out.println(c); Constructor ctr = c.getConstructor(InvocationHandler.class); Object m = ctr.newInstance(h); //m.move(); return m; } }
以这种方式再原来的业务基础上加逻辑,可扩展性好,可以很方便添加和撤销。像struts2里面的拦截器,Spring中的AOP,都是动态代理的一种应用。
当然,动态代理在JDK中也有自己的实现。在java.lang.reflect包中可以找到。