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

来源:互联网 发布:淘宝助理有客服吗 编辑:程序博客网 时间:2024/06/11 09:32

[ 需求分析 ]

在我们实际开发中常常会遇到这样的问题:记录一个类的方法运行时间,以分析性能。一般我们的做法是先在类的开始记录一个开始时间,然后在类的结束记录一个结束时间,二者相减就可以获取我们想要的结果。但是很多时候这些类已经打了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 solwlyThread.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;}@Overridepublic 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;}@Overridepublic 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 interfaceMethod[] 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 instanceConstructor 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;}@Overridepublic 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:实现对被代理对象的处理。


[ 应用场景 ]

① 系统日志记录

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

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


[ 特别感谢 ]

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