关闭

【设计模式】动态代理Proxy_01

标签: 设计模式动态代理ProxyProxy原理剖析
583人阅读 评论(0) 收藏 举报
分类:
大家都知道设计模式中一个比较难理解的模式--动态代理模式,下面我们来通过一步一步完善一个工程来学习动态代理。

首先我们创建一个JavaProject,名字为"ProxyTest"。

创建一个类Tank.java,是一个坦克类,然后我们创建一个接口Moveable.java
Moveable.java:
package cn.edu.hpu.proxy;

public interface Moveable {
 void move();
}

Tank类实现Moveable接口
Tank.java:
package cn.edu.hpu.proxy;

import java.util.Random;

public class Tank implements Moveable{

  @Override
  public void move() {
    System.out.println("坦克正在移动中...");
      try {
            //睡眠10秒以内的随机时间,表示坦克正在移动中
            Thread.sleep(new Random().nextInt(10000));
          } catch (InterruptedException e) {
          // TODO Auto-generated catch block
              e.printStackTrace();
          }
  }

}

下面问题来了:
我想知道一个方法的运行时间,如何得到?

大家很容易想到的是,在方法刚刚执行后和就要结束之前计算一下当前时间,然后相减获得执行时间:

@Override
public void move() {
	long start=System.currentTimeMillis();
	System.out.println("坦克正在移动中...");
	try {
		//睡眠10秒以内的随机时间,表示坦克正在移动中
		Thread.sleep(new Random().nextInt(10000));
	} catch (InterruptedException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	long end=System.currentTimeMillis();
	System.out.println("运行时间:"+(end-start)+"ms");
}
那么,如果不允许修改move中的方法呢?比如说这个方法是别人的包里提供给你的,是人家已经写好的源代码,就编译了class给你,就没有给你源码,你没法在源码上修改怎么办?解决这件事情之前,我们先写一个测试类:
package cn.edu.hpu.proxy;

public class Client {
    public static void main(String[] args) {
         Moveable m=new Tank();
         m.move();
    }
}
不要求计算move()方法执行的时间(JDK为其准备的时间等...),要计算内部代码执行的时间。

我们再写一个Tank2,继承Tank
package cn.edu.hpu.proxy;

public class Tank2 extends Tank{

     @Override
     public void move() {
         long start=System.currentTimeMillis();
         super.move();
         long end=System.currentTimeMillis();
         System.out.println("运行时间:"+(end-start)+"ms");
     }

}
用继承来实现把原来的方法前后加一些逻辑,这是一种方式,是没有问题的。

除了上面这种方式之外,还有另外一种方式:
写一个Tank3,实现和Tank一样的接口
package cn.edu.hpu.proxy;

public class Tank3 implements Moveable{
	Tank t;

	public Tank3(Tank t) {
		super();
		this.t = t;
	}

	@Override
	public void move() {
		long start=System.currentTimeMillis();
		t.move();
		long end=System.currentTimeMillis();
		System.out.println("运行时间:"+(end-start)+"ms");
		
	}

}
上面一个使用继承来实现,一个是一个类存有另外一个类的对象,叫聚合。
这两种都是简单的代理的实现。

现在请问大家:认为这两种方式那种更好?为什么?
聚合好。为什么呢?
下面来看看。

除了时间的代理之外,还有其他的各种各样的代理,如在方法开始前后做日志记录,或加一段事务控制,或检查运行权限等。

由于Tank3它是记录时间的,而且是Tank的一个代理类,所以我们给它换一个名字,叫"TankTimeProxy"类。

package cn.edu.hpu.proxy;

public class TankTimeProxy implements Moveable{
	Tank t;

	public TankTimeProxy(Tank t) {
		super();
		this.t = t;
	}

	@Override
	public void move() {
		long start=System.currentTimeMillis();
		System.out.println("开始时间:"+start+"ms");
		t.move();
		long end=System.currentTimeMillis();
		System.out.println("运行时间:"+(end-start)+"ms");
		
	}

}


我们再写一个类是对Tank类加日志信息的日志代理类:

package cn.edu.hpu.proxy;

public class TankLogProxy implements Moveable{
	Tank t;

	public TankLogProxy(Tank t) {
		super();
		this.t = t;
	}

	@Override
	public void move() {
		System.out.println("坦克准备移动...");
		t.move();
		System.out.println("坦克移动结束...");
	}

}
下面我们要对日志进行叠加,规定先先记录时间,再记录日志,怎么办?需要在写一个Tank3去继承Tank2(里面已经包含了时间记录代理),然后把日志记录代码加在move方法前后。如果规定先记录日志,再记录时间,怎么办?又要创建一个Tank4,继承Tank类,然后把日志记录代码加在move方法前后,之后再创建Tank5继承自Tank4,在在move方法前后加上时间记录代码.....你会发现,用继承的方式实现代理的话,这个类会无限制的增加下去,各种各样的代理功能如果想实现叠加的话,这个类要无限的往下继承,无边无际了...

所以,回到刚刚的话题,继承并不是好的实现代理的方式,聚合比较合适。
接下来看看聚合的优点。首先我们把TankTimeProxy和TankLogProxy中的Tank引用类改成Moveable接口(包括构造函数的参数类型)。
拿出TankTimeProxy与TankLogProxy看看:

TankTimeProxy:

package cn.edu.hpu.proxy;

public class TankTimeProxy implements Moveable{
	Moveable t;

	public TankTimeProxy(Moveable t) {
		super();
		this.t = t;
	}

	@Override
	public void move() {
		long start=System.currentTimeMillis();
		System.out.println("开始时间:"+start+"ms");
		t.move();
		long end=System.currentTimeMillis();
		System.out.println("运行时间:"+(end-start)+"ms");
		
	}

}

TankLogProxy:
package cn.edu.hpu.proxy;

public class TankLogProxy implements Moveable{
	Tank t;

	public TankLogProxy(Tank t) {
		super();
		this.t = t;
	}

	@Override
	public void move() {
		System.out.println("坦克准备移动...");
		t.move();
		System.out.println("坦克移动结束...");
	}

}


我们其中的引用类Moveable t;既可以是实现了Moveable接口的Tank类,又可以是实现了Moveable接口的代理类,那么,代理之间的嵌套是不是可以实现呢?可以,加入我们想在日志代理外面嵌套时间代理,即先记录时间后记录日志,那么我们直接把引用类Moveable t;的实现指向日志代理类TankLogProxy即可。
即:
Moveable t=new TankTimeProxy(new TankLogProxy(new Tank()));
t.move();
运行TankTimeProxy的move方法时,
@Override
public void move() {
long start=System.currentTimeMillis();
System.out.println("开始时间:"+start+"ms");
//此时t指向TankLogProxy类,调用的是TankLogProxy类的move方法
t.move();
long end=System.currentTimeMillis();
System.out.println("运行时间:"+(end-start)+"ms");

}
我们知道TankLogProxy类的move方法是这样的(也就是上面的t.move();):
@Override
public void move() {
System.out.println("坦克准备移动...");
//这里的t指向的是Tank,所以执行的是Tank的move方法
t.move();
System.out.println("坦克移动结束...");
}


把TankTimeProxy的move方法中的t.move();解刨出来就是:
</pre><pre name="code" class="java">@Override
public void move() {
	long start=System.currentTimeMillis();
	System.out.println("开始时间:"+start+"ms");


	System.out.println("坦克准备移动...");
	//Tank的move方法
	t.move();
	System.out.println("坦克移动结束...");


	long end=System.currentTimeMillis();
	System.out.println("运行时间:"+(end-start)+"ms");
		
}

注意:此时Tank类(没加任何代理,只有最原始的移动方法):
package cn.edu.hpu.proxy;

import java.util.Random;

public class Tank implements Moveable{

	@Override
	public void move() {
		System.out.println("坦克正在移动中...");
		try {
			//睡眠10秒以内的随机时间,表示坦克正在移动中
			Thread.sleep(new Random().nextInt(10000));
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

测试:
package cn.edu.hpu.proxy;

public class Client {
	public static void main(String[] args) {
		Moveable t=new TankTimeProxy(new TankLogProxy(new Tank()));
		t.move();
	}
}

测试结果:
开始时间:1435309953538ms
坦克准备移动...
坦克正在移动中...
坦克移动结束...
运行时间:1529ms

此时就实现了时间和日志代理的嵌套,当然还可以嵌套更多的代理逻辑,是不是比继承方便多了?


以后为了方便管理,也可以将代理逻辑的先后通过配置文件来配置。
关键就是实现统一接口。

我们接下来继续讨论(以后只考虑TankTimeProxy)。如果Moveable接口里还有一个方法,stop()停止方法,Tank必须实现这个方法,那么我要知道stop()方法运行的时间,还要加之前写的时间代理代码:
首先是Moveable接口
package cn.edu.hpu.proxy;

public interface Moveable {
	void move();
	void stop();
}

Tank类
package cn.edu.hpu.proxy;

import java.util.Random;

public class Tank implements Moveable{

	@Override
	public void move() {
		System.out.println("坦克正在移动中...");
		try {
			//睡眠10秒以内的随机时间,表示坦克正在移动中
			Thread.sleep(new Random().nextInt(10000));
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	@Override
	public void stop() {
		System.out.println("坦克停止中...");
		
	}
	
}

TankTimeProxy类
package cn.edu.hpu.proxy;


public class TankTimeProxy implements Moveable{
	Moveable t;


	public TankTimeProxy(Moveable t) {
		super();
		this.t = t;
	}


	@Override
	public void move() {
		long start=System.currentTimeMillis();
		System.out.println("开始时间:"+start+"ms");
		t.move();
		long end=System.currentTimeMillis();
		System.out.println("运行时间:"+(end-start)+"ms");
		
	}


	@Override
	public void stop() {
		long start=System.currentTimeMillis();
		System.out.println("开始时间:"+start+"ms");
		t.stop();
		long end=System.currentTimeMillis();
		System.out.println("运行时间:"+(end-start)+"ms");
	}
}

这个时候我们注意了,当我们平时在写程序的时候,如果你发现有一段代码你总是在那里写,这个方法里有,那个方法里也有,这个时候我们就要考虑将这段代码封装起来了。
这里我们发现TankTimeProxy类中有关时间记录的代码有重复,我们将其封装起来:
package cn.edu.hpu.proxy;

public class TankTimeProxy implements Moveable{
	Moveable t;
	long start;
	long end;

	public TankTimeProxy(Moveable t) {
		super();
		this.t = t;
	}

	public void before(){
		start=System.currentTimeMillis();
		System.out.println("开始时间:"+start+"ms");
	}
	
	public void after(){
		end=System.currentTimeMillis();
		System.out.println("运行时间:"+(end-start)+"ms");
	}
	
	@Override
	public void move() {
		this.before();
		t.move();
		this.after();
	}


	@Override
	public void stop() {
		this.before();
		t.stop();
		this.after();
	}
}

下面我们考虑一个很难的问题,假如说我想让TankTimeProxy这个代理叫做TimeProxy,意味着它不仅可以计算Tank类的运行方法,还可以把任何一个对象当做被代理对象,可以计算任何代理类的方法运行的时间。(至于为什么会有这种需求,比如说我们又加了一个交通工具Car,我们要对Car计算运行时间的话,要重写一个CarTimeProxy代理,如果还有上百种类型的类,对不同的类实现时间代理,你要写上百种代理类!!!)。

所以我们要实现一种"通用"时间代理,可以对任意对象代理。

我们可以使用Java的反射机制,具体方式详见总结02

转载请注明出处:http://blog.csdn.net/acmman/article/details/46827819

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:982025次
    • 积分:17913
    • 等级:
    • 排名:第526名
    • 原创:815篇
    • 转载:18篇
    • 译文:0篇
    • 评论:468条
    关于我
    就职:上海远成物流信息部
    职位:中级Java开发工程师
    负责:物流系统开发与维护
    院校:河南理工大学
    专业:软件工程12级
    邮箱:jackZhuCoder@126.com
    Q Q :10101000101001010111
    1101111010
    博客专栏
    最新评论