zoukankan      html  css  js  c++  java
  • 继承?静态代理?写一个自己的动态代理吧

    版权声明: https://blog.csdn.net/zdp072/article/details/24868895

    [ 需求分析 ]

    在我们实际开发中经常会遇到这种问题:记录一个类的方法运行时间。以分析性能。

    一般我们的做法是先在类的開始记录一个開始时间,然后在类的结束记录一个结束时间,二者相减就能够获取我们想要的结果。

    可是非常多时候这些类已经打了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 = "
    	" ;
    		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:实现对被代理对象的处理。


    [ 应用场景 ]

    ① 系统日志记录

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

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


    [ 特别感谢 ]

    这篇日志是对马士兵老师:《设计模式之动态代理》的一个总结,非常感谢马老师的辛勤工作。
查看全文
  • 相关阅读:
    求正整数N(N>1)的质因数的个数。
    手机键盘输入字母
    第二部分进度
    第一部分:地域维度标准化
    利用python解析地址经纬度
    输入任意4个字符(如:abcd), 并按反序输出(如:dcba)
    python-->微信支付
    python-图片流传输(url转换二维码)
    python-qrcode-二维码
    ajax和axios、fetch的区别
  • 原文地址:https://www.cnblogs.com/ldxsuanfa/p/10479519.html
  • Copyright © 2011-2022 走看看