【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设计模式】设计模式之 代理模式_第1张图片

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













你可能感兴趣的:(Java设计模式)