【Java设计模式】设计模式之 代理模式

设计模式之 代理模式

 

定义:为其他对象提供一种代理以控制对这个对象的访问。代理对象起到中介作用,可去掉功能服务或增加额外的服务。

 

代理模式的分类

 

虚拟代理

远程代理

保护代理

智能引用代理

 

智能引用代理

 

静态代理:代理对象和被代理对象在代理之前都是确定的。他们都实现相同的接口或者继承相同的抽象类。

 

有两种实现方式。

1.      通过继承实现。

2.      通过聚合实现。

 

情景案例:

我们有一个车类,车具有行驶的方法。通过代理,增加记录行驶时间的方法。

1.定义接口 Moveable.java

/*
 * 模拟行驶的接口
 */
public interface Moveable {
	void move();
}
2.创建一个车类 Car.java 实现这个接口。
public class Car implements Moveable {
	public void move() {
		//实现开车
		try {
			Thread.sleep(new Random().nextInt(1000));
			System.out.println("汽车行驶中");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

3.现在我们为车类增加一个记录行驶时间的处理,创建一个代理类

用继承的方式实现。

public class Car2 extends Car {
	public void move() {
		long startTime = System.currentTimeMillis();
		System.out.println("汽车开始行驶");
		super.move();//调用父类的方法,这就实现了Car2 对Car的代理
		long endTime = System.currentTimeMillis();
		System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");
	}
}
用聚合的方式实现

/*
 * 使用聚合的方式实现静态代理,聚合就是在一个类中使用到另一个类的对象
 */
public class Car3 implements Moveable {
	public Car3(Car car){
		super();
		this.car = car;
	}
	private Car car;
	public void move() {
		long startTime = System.currentTimeMillis();
		System.out.println("汽车开始行驶");
		
		car.move();//使用聚合的方式,把参数传进来进行调用
		
		long endTime = System.currentTimeMillis();
		System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");
	}
}

继承和聚合实现静态代理,那个更合适?

如果我们要在记录行驶时间前增加记录日志功能,则需要创建Car4.java 继承Car2 或者继承Car,如果在增加其他的功能,则代理类就会无限的膨胀,越来越多。所以使用聚合的方式更适合用来实现静态代理。

使用聚合的方式实现静态代理

1.创建日志记录代理类

/*
 * 使用聚合的方式实现静态代理,聚合就是在一个类中使用到另一个类的对象
 * 汽车日志的代理类
 */
public class CarLogProxy implements Moveable {
	public CarLogProxy(Moveable moveable){
		super();
		this.moveable = moveable;
	}
	private Moveable moveable;
	public void move() {
		System.out.println("日志开始");
		moveable.move();//使用聚合的方式,把参数传进来进行调用
		System.out.println("日志结束");
	}
}
2.创建行驶时间代理类
/*
 * 使用聚合的方式实现静态代理,聚合就是在一个类中使用到另一个类的对象
 */
public class CarTimeProxy implements Moveable {
	public CarTimeProxy(Moveable moveable){
		super();
		this.moveable = moveable;
	}
	private Moveable moveable;
	public void move() {
		long startTime = System.currentTimeMillis();
		System.out.println("汽车开始行驶");
		moveable.move();//使用聚合的方式,把参数传进来进行调用
		long endTime = System.currentTimeMillis();
		System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");
	}
}
3.测试类,先记录日志,再记录行驶的时间。
/*
 * 测试类
 */
public class CTest {
	public static void main(String[] args) {
		Car car = new Car();
		CarTimeProxy ctp = new CarTimeProxy(car);
		CarLogProxy clp = new CarLogProxy(ctp);
		clp.move();
	}
}

4.输出结果

我们可以自由组合这种方式,如先记录时间再记录日志。

public class CTest {
	public static void main(String[] args) {
		Car car = new Car();
		CarLogProxy clp = new CarLogProxy(car);
		CarTimeProxy ctp = new CarTimeProxy(clp);
		ctp.move();
	}
}

思考:如果还要为火车、自行车实现代理类,那么还要再按照上面的方式再分别实现两个类。有没有办法可以让一个类代理火车、自行车、汽车呢?

 

动态产生代理,实现对不同类,不同方法的代理。

所谓Dynamic Proxy 是这样一种class:

它是在运行时生成的class

该class需要实现一组interface

使用动态代理类时,必须实现InvocationHandler接口

JDK动态代理

Java 动态代理类位于java.lang.reflect包下,一般主要涉及以下两个类:

①  Interface InvocationHandler:该接口中仅定义了一个方法

public object invoke(Objectobj,Method method,Object[] args)

在实际使用时,第一个参数obj表示最终生成的代理类对象,method是被代理的方法,args为该方法的参数数组。这个抽象方法在代理类中动态实现。

②  Proxy: 该类即为动态代理类

static ObjectnewProxyInstance(ClassLoader loader,Calss[] interface, InvocationHandler h): 返回代理类的一个实例,返回后的代理类可以当做被代理类使用(可使用被代理类在接口中声明过的方法)

 

JDK动态代理实现步骤

1.      创建一个实现接口InvocationHandler的类,它必须实现invoke方法。

2.      创建被代理的类以及接口。

3.      调用Proxy的静态方法,创建一个代理类

newProxyInstance(ClassLoaderloader,Class[] interfaces,InvocationHandler h)

4.      通过代理调用方法。

 

1创建TimeHandler.java 实现InvocationHandler 接口

public class TimeHandler implements InvocationHandler {
	public TimeHandler(Object target){
		super();
		this.target = target;
	}
	private Object target;
	/*
	 * 参数:
	 * proxy    代理类的对象
	 * method  被代理对象的方法
	 * args     方法的参数
	 * 
	 * 返回值:
	 * Object 方法的返回值
	 */
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		long startTime = System.currentTimeMillis();
		System.out.println("汽车开始行驶");
		method.invoke(target);
		long endTime = System.currentTimeMillis();
		System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");
		return null;
	}
}

模拟JDK动态代理实现思路

 

动态代理实现思路

实现功能:通过Proxy 的 newProxyInstance返回代理对象。

1.      声明一段源码(动态产生代理)

2.      编译源码(JDK Compiler API),产生新的类(代理类)

3.      将这个类load到内存当中,产生一个新的对象(代理对象)

4.      return 代理对象。

 

初步实现:首先我们自己创建一个Proxy类,并创建newProxyInstance方法

public class Proxy {
	/*
	 * 创建一个方法用来返回我们的代理对象
	 */
	public static Object newProxyInstance(Class infce) throws Exception{
		String rt = "\r\n";//windows下的换行符
		/*
		 * 第一步:声明一段源码
		 * 替换interface的名字
		 */
		
		//获取我们的方法
		String methodStr ="";
		for(Method m : infce.getMethods()){
			methodStr +="public void "+m.getName()+"() {"+rt+
			"long startTime = System.currentTimeMillis();"+rt+
			"System.out.println(\"汽车开始行驶\");"+rt+
			"moveable."+m.getName()+"();//使用聚合的方式,把参数传进来进行调用"+rt+
			"long endTime = System.currentTimeMillis();"+rt+
			"System.out.println(\"汽车结束行驶,行驶时间:\"+(endTime - startTime)+\"毫秒\");"+rt+
		"}";
		}
		
		String str=
		"package com.meng.proxy;"+rt+
		"public class $Proxy0 implements "+infce.getName()+"{"+rt+
			"public $Proxy0("+infce.getName()+" moveable){"+rt+
				"super();"+rt+
				"this.moveable = moveable;"+rt+
			"}"+rt+
			"private "+infce.getName()+" moveable;"+rt+
			 methodStr+rt+
		"}";
		/*
		 * 第二步:生成一个文件
		 */
		String filename = System.getProperty("user.dir")+"/bin/com/meng/proxy/$Proxy0.java";
		File file = new File(filename);
		//调用commons.io.jar 的方法写文件
		FileUtils.write(file, str);
		/*
		 * 第三步:编译生成的java类
		 */
		/*
		 * 这里需要把jre 改为jdk
		 */
		//得到当前系统的编译器
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		//文件管理者
		StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
		//根据文件名得到文件的数据
		Iterable units = fileManager.getJavaFileObjects(filename);
		//编译任务
		CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);
		//进行编译
		t.call();
		fileManager.close();
		/*
		 * 第四步:把编译好的文件load到内存
		 */
		ClassLoader cl = ClassLoader.getSystemClassLoader();
		Class c = cl.loadClass("com.meng.proxy.$Proxy0");
		
		Constructor ctr = c.getConstructor(infce);
        //根据构造方法把Car传进去
		return ctr.newInstance(new Car());
	}
}

分欣上面这段代码,我们传递了一个接口拼接了一段代理类的源码$Proxy0

把这段源码写入$Proxy0.java 文件并编译这个文件,并把编译好的文件加载到内存,获取构造方法并创建代理类的对象,返回这个代理类对象。

bin目录下生成的文件


拼接源码文件的内容:
package com.meng.proxy;
public class $Proxy0 implements com.meng.proxy.Moveable{
public $Proxy0(com.meng.proxy.Moveable moveable){
super();
this.moveable = moveable;
}
private com.meng.proxy.Moveable moveable;
public void move() {
long startTime = System.currentTimeMillis();
System.out.println("汽车开始行驶");
moveable.move();//使用聚合的方式,把参数传进来进行调用
long endTime = System.currentTimeMillis();
System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");
}
}
测试类

public class CTest {
	public static void main(String[] args) throws Exception {
		Moveable m = (Moveable)Proxy.newProxyInstance(Moveable.class);
		m.move();
	}
}

运行结果

分析上面的实现方法,我们发现增加业务逻辑的实现写死在了代码里,而且构造函数那传入的是new Car() 这样就不具有通用性,下面对这种方法进行扩展改进,进行解耦

 

改进实现:

创建InvocationHandler 接口

public interface InvocationHandler {
    //第一个参数表示代理类对象,第二个参数表示方法对象,省略方法参数
	public void invoke(Object o,Method m);
}
创建TimeHandler 实现这个接口,在这里增加我们业务处理逻辑

public class TimeHandler implements InvocationHandler {
	//被代理的对象
	private Object target;
	public TimeHandler(Object object){
		super();
		this.target = object;
	}
	@Override
	public void invoke(Object o, Method m) {
		try {
			long startTime = System.currentTimeMillis();
			System.out.println("汽车开始行驶");
			m.invoke(target);
			long endTime = System.currentTimeMillis();
			System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}
}
改写我们的 Proxy.java

public class Proxy {
	/*
	 * 创建一个方法用来返回我们的代理对象
	 */
	public static Object newProxyInstance(Class infce,InvocationHandler h) throws Exception{
		String rt = "\r\n";//windows下的换行符
		/*
		 * 第一步:声明一段源码
		 * 替换interface的名字
		 */
		
		//获取我们的方法
		String methodStr ="";
		for(Method m : infce.getMethods()){
			methodStr +="public void "+m.getName()+"() {"+rt+
			"try{"+rt+
		    "Method md = "+infce.getName()+".class.getMethod(\""+m.getName()+"\");"+rt+
		    "h.invoke(this,md);"+rt+
		    "}catch(Exception e){e.printStackTrace();}"+rt+
		"}"+rt;
		}
		
		String str=
		"package com.meng.proxy;"+rt+
		"import com.meng.proxy.InvocationHandler;"+rt+
		"import java.lang.reflect.Method;"+rt+
		"public class $Proxy0 implements "+infce.getName()+"{"+rt+
			"public $Proxy0(InvocationHandler h){"+rt+
				"super();"+rt+
				"this.h = h;"+rt+
			"}"+rt+
			"private InvocationHandler h;"+rt+
			 methodStr+rt+
		"}";
		/*
		 * 第二步:生成一个文件
		 */
		String filename = System.getProperty("user.dir")+"/bin/com/meng/proxy/$Proxy0.java";
		File file = new File(filename);
		//调用commons.io.jar 的方法写文件
		FileUtils.write(file, str);
		/*
		 * 第三步:编译生成的java类
		 */
		/*
		 * 这里需要把jre 改为jdk
		 */
		//得到当前系统的编译器
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		//文件管理者
		StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
		//根据文件名得到文件的数据
		Iterable units = fileManager.getJavaFileObjects(filename);
		//编译任务
		CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);
		//进行编译
		t.call();
		fileManager.close();
		/*
		 * 第四步:把编译好的文件load到内存
		 */
		ClassLoader cl = ClassLoader.getSystemClassLoader();
		Class c = cl.loadClass("com.meng.proxy.$Proxy0");
		
		Constructor ctr = c.getConstructor(InvocationHandler.class);
		return ctr.newInstance(h);
	}
}
重新生成的代理类 $Proxy0.java 如下:

public class $Proxy0 implements com.meng.proxy.Moveable{
public $Proxy0(InvocationHandler h){
super();
this.h = h;
}
private InvocationHandler h;
public void move() {
try{
Method md = com.meng.proxy.Moveable.class.getMethod("move");
h.invoke(this,md);
}catch(Exception e){e.printStackTrace();}
}

}
测试类:

public class CTest {
	public static void main(String[] args) throws Exception {
		Car car = new Car();
		InvocationHandler h = new TimeHandler(car);
		Moveable m = (Moveable)Proxy.newProxyInstance(Moveable.class,h);
		m.move();
	}
}

运行结果:

这样我们的动态代理就具有了扩展性

理解的还不是很清楚,如有疑问请移步

慕课网地址:http://www.imooc.com/learn/214













  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
目录 第1章UML类图实训 1.1知识讲解 1.1.1UML概述 1.1.2类与类的UML表示 1.1.3类之间的关系 1.2实训实例 1.2.1类图实例之图书管理系统 1.2.2类图实例之商场会员管理系统 1.3实训练习 第2章面向对象设计原则实训 2.1知识讲解 2.1.1面向对象设计原则概述 2.1.2单一职责原则 2.1.3开闭原则 2.1.4里氏代换原则 2.1.5依赖倒转原则 2.1.6接口隔离原则 2.1.7合成复用原则 2.1.8迪米特法则 2.2实训实例 2.2.1单一职责原则实例分析 2.2.2开闭原则实例分析 2.2.3里氏代换原则实例分析 2.2.4依赖倒转原则实例分析 2.2.5接口隔离原则实例分析 2.2.6合成复用原则实例分析 2.2.7迪米特法则实例分析 2.3实训练习 第3章创建型模式实训 3.1知识讲解 3.1.1设计模式 3.1.2创建型模式概述 3.1.3简单工厂模式 3.1.4工厂方法模式 3.1.5抽象工厂模式 3.1.6建造者模式 3.1.7原型模式 3.1.8单例模式 3.2实训实例 3.2.1简单工厂模式实例之图形工厂 3.2.2工厂方法模式实例之日志记录器 3.2.3抽象工厂模式实例之数据库操作工厂 3.2.4建造者模式实例之游戏人物角色 3.2.5原型模式实例之快速创建工作周报 3.2.6单例模式实例之多文档窗口 3.3实训练习 第4章结构型模式实训 4.1知识讲解 4.1.1结构型模式概述 4.1.2适配器模式 4.1.3桥接模式 4.1.4组合模式 4.1.5装饰模式 4.1.6外观模式 4.1.7享元模式 4.1.8代理模式 4.2实训实例 4.2.1适配器模式实例之算法适配 4.2.2桥接模式实例之跨平台视频播放器 4.2.3组合模式实例之杀毒软件 4.2.4装饰模式实例之界面显示构件库 4.2.5外观模式实例之文件加密 4.2.6享元模式实例之围棋棋子 4.2.7代理模式实例之日志记录代理 4.3实训练习 第5章行为型模式实训 5.1知识讲解 5.1.1行为型模式概述 5.1.2职责链模式 5.1.3命令模式 5.1.4解释器模式 5.1.5迭代器模式 5.1.6中介者模式 5.1.7备忘录模式 5.1.8观察者模式 5.1.9状态模式 5.1.10策略模式 5.1.11模板方法模式 5.1.12访问者模式 5.2实训实例 5.2.1职责链模式实例之在线文档帮助系统 5.2.2命令模式实例之公告板系统 5.2.3解释器模式实例之机器人控制程序 5.2.4迭代器模式实例之商品名称遍历 5.2.5中介者模式实例之温度转换器 5.2.6备忘录模式实例之游戏恢复点设置 5.2.7观察者模式实例之股票变化 5.2.8状态模式实例之银行账户 5.2.9策略模式实例之电影票打折 5.2.10模板方法模式实例之数据库操作 5.2.11访问者模式实例之奖励审批 5.3实训练习 第6章模式联用与综合实例实训 6.1设计模式补充知识 6.1.1反射与配置文件 6.1.2GRASP模式 6.1.3架构模式与MVC 6.2模式联用实训 6.2.1适配器模式与桥接模式联用 6.2.2组合模式与命令模式联用 6.2.3外观模式与单例模式联用 6.2.4原型模式与备忘录模式联用 6.2.5观察者模式与组合模式联用 6.2.6访问者模式、组合模式与迭代器模式联用 6.3综合实例实训 6.3.1多人联机射击游戏 6.3.2数据库同步系统 6.4实训练习 附录A参考答案 A.1第1章实训练习参考答案 A.2第2章实训练习参考答案 A.3第3章实训练习参考答案 A.4第4章实训练习参考答案 A.5第5章实训练习参考答案 A.6第6章实训练习参考答案 参考文献

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值