[ 需求分析 ]
在我们实际开发中常常会遇到这样的问题:记录一个类的方法运行时间,以分析性能。一般我们的做法是先在类的开始记录一个开始时间,然后在类的结束记录一个结束时间,二者相减就可以获取我们想要的结果。但是很多时候这些类已经打了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(); } } }
/** * 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等等,能不能让计算机帮我们产生这些类?自己动手试试吧!
[ 我的动态代理 ]
① 所谓的动态代理,就是说上文的TankTimeProxy,TankLogProxy不需要我们手动创建了,计算机会帮我们动态生成,也就是说这个代理类不是提前写好的,而是程序运行时动态生成的。
我们写一个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:实现对被代理对象的处理。
[ 应用场景 ]
① 系统日志记录② 权限控制(符合一定条件才执行某方法)
③ 事务控制(方法执行之前开启事务,方法执行之后提交或回滚事务)
[ 特别感谢 ]
这篇日志是对马士兵老师:《设计模式之动态代理》的一个总结,非常感谢马老师的辛勤工作。继承?静态代理?写一个自己的动态代理吧,布布扣,bubuko.com
原文:http://blog.csdn.net/zdp072/article/details/24868895