继承?静态代理?写一个自己的动态代理吧
Posted ldxsuanfa
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了继承?静态代理?写一个自己的动态代理吧相关的知识,希望对你有一定的参考价值。
[ 需求分析 ]
在我们实际开发中经常会遇到这种问题:记录一个类的方法运行时间。以分析性能。
一般我们的做法是先在类的開始记录一个開始时间,然后在类的结束记录一个结束时间,二者相减就能够获取我们想要的结果。
可是非常多时候这些类已经打了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:实现对被代理对象的处理。
[ 应用场景 ]
① 系统日志记录② 权限控制(符合一定条件才运行某方法)
③ 事务控制(方法运行之前开启事务。方法运行之后提交或回滚事务)
[ 特别感谢 ]
这篇日志是对马士兵老师:《设计模式之动态代理》的一个总结,非常感谢马老师的辛勤工作。