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

Posted ldxsuanfa

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了继承?静态代理?写一个自己的动态代理吧相关的知识,希望对你有一定的参考价值。

版权声明: 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 = "\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:实现对被代理对象的处理。


[ 应用场景 ]

① 系统日志记录

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

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


[ 特别感谢 ]

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

以上是关于继承?静态代理?写一个自己的动态代理吧的主要内容,如果未能解决你的问题,请参考以下文章

java 代理模式(静态代理动态代理Cglib代理) 转载

什么静态/动态代理,内容详解,只要看就会懂

动态代理

简单jdk动态代理

简单jdk动态代理

java中的静态代理