Java设计模式--代理模式

  • 概述

我们平常买火车票的时候是不是就只有三种方式:火车站售票厅,火车票代售处,网上购票,但是在很多年前的话就是前两种方式。其实火车票代售处就是火车站售票厅的代理,并且火车票代售处可以提供额外的服务,比如电话预约,并且我们知道火车票代售处是不负责票的退换的,所以这就要说到代理模式的几种模式了(请看概念)。

概念

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

常见代理模式定义
远程代理为不同地理的对象提供局域网代表对象
虚拟代理根据需要将资源消耗很大的对象进行延迟,当真正需要的时候才进行创建
保护代理权限控制
智能引用代理根据需要可去掉功能服务或增加额外的服务

接下来我们就以智能引用代理来进行实现,智能引用代理模式主要分为静态代理和动态代理,接下来我们就分别用这两种方式实现功能。

静态代理代码实现

静态代理:代理和被代理对象在代理之前是确定的。他们都实现相同接口或者继承相同的抽象类。
类图
静态代理有两种实现方法,一种是继承的方法,另一种是组合的方法,但是不管怎么样首先定义一个接口作为基类。
Moveable.java

package com.xjh.proxy;

public interface Moveable {
	
	void move();
}

之后定义一个类实现Moveable接口,这个类作为被代理的类。
Car.java

package com.xjh.proxy;

import java.util.Random;

public class Car implements Moveable {

	@Override
	public void move() {
		// 实现开车
		try {
			Thread.sleep(new Random().nextInt(1000));
			System.out.println("汽车行驶中...");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

之后就是实现代理类,这里就有两种方法了:一种是继承,另一种是组合。
CarTimeProxy1.java(继承)

package com.xjh.proxy;

public class CarTimeProxy1 extends Car {

	//实现了Car2对Car的代理,继承方法
	public void move() {
		long starttime = System.currentTimeMillis();
		System.out.println("汽车开始行驶...");
		super.move();
		long endtime = System.currentTimeMillis();
		System.out.println("汽车结束行驶... 继承方法实现汽车行驶时间:" + ( endtime - starttime) + "ms");
	}
	
}

CarTimeProxy2.java(组合)

package com.xjh.proxy;

import java.util.Random;

public class CarTimeProxy2 implements Moveable {
	//使用组合的方法实现代理
	private Moveable m;
	
	public CarLogProxy(Moveable m) {
		super();
		this.m = m;
	}

	@Override
	public void move() {
		long starttime = System.currentTimeMillis();
		System.out.println("汽车开始行驶...");
		m.move();
		long endtime = System.currentTimeMillis();
		System.out.println("汽车结束行驶... 组合实现方法汽车行驶时间:" + ( endtime - starttime) + "ms");
	}

}

接下来我们就测试一下。
Client.java

package com.xjh.proxy;

public class Client {

	public static void main(String[] args) {
		CarTimeProxy1 car1 = new CarTimeProxy1();
		car1.move();
		Moveable car2 = new CarTimeProxy2(new Car());
		car2.move();
	}

}

测试结果
当然我们要对比一下这两种方法实现静态代理有什么区别,那个更加适合代理模式,其实我们可以想象一种情况,假如我们需要添加功能两种方法都该这么实现呢?

  • 继承:我们就要无限的新建一个类,继承于之前的类,这样的话如果功能增加过多,并且有很多重复组合,代理类会无限的膨胀下去,并且重复的代码会很多,无法达到代码复用的效果。(不推荐使用)
  • 组合:我们只需要实现Moveable接口,然后不断地嵌套就可以。

因为继承的方式实现很简单,就是无限的重复CarTimeProxy1的编写方式,我们就不来编写相关代码了。下面我们就来看看组合是怎么处理这种方式的。
假如我们要加上打印日志的功能,我们只需要再定义一个类实现Moveable接口。
CarLogProxy.java

package com.xjh.proxy;

import java.util.Random;

public class CarLogProxy implements Moveable {
	//使用组合的方法实现代理
	private Moveable m;
	
	public CarLogProxy(Moveable m) {
		super();
		this.m = m;
	}

	@Override
	public void move() {
		long starttime = System.currentTimeMillis();
		System.out.println("日志开始...");
		m.move();
		long endtime = System.currentTimeMillis();
		System.out.println("日志结束...");
	}

}

之后我们在main方法中new对象的时候传入对象。
Client.java

package com.xjh.proxy;

public class Client {

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

}

执行结果
这样我们就实现了功能的拓展,如果我们想要先移动再记录日志就没有必要重新编写一个类了,直接修改就好了。
Client.java

package com.xjh.proxy;

public class Client {

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

}

执行结果
这样我们就解决了日志和时间交换顺序时不需要再新建类,但是每种功能在不同的类,比如汽车和火车,我们还是要新建代理类,假如我们要加入很多的功能,这样我们的代理类还是很多,有什么办法解决嘛?答案是动态代理。

动态代理实现代码

动态代理模式类图:
类图
我们可以通过下面的类图发现,动态代理的实现就是在代理类和被代理类之间加入了一个InvocationHandler,而InvocationHandler接口需要我们实现invoke方法,他有三个参数,我们来分别理解一下三个参数以及返回对象对应的内容:

名称内容
proxy生成的代理对象
method被代理对象的方法
args方法的参数
Object方法的返回值

接下来我们就来实现这个类,我们只需要在里面调用method.invoke(target)方法,之后在前后实现这个功能的相关代码。
TimeHandler.java

package com.xjh.jdkproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler {
	
	private Object target;
	
	public TimeHandler(Object target) {
		super();
		this.target = target;
	}

	@Override
	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) + "ms");
		return null;
	}

}

之后我们编写main函数,我们主要就是调用Proxy.newProxyInstance(loader, interfaces, h)方法自动生成代理类,里面参数的含义如下:

名称含义
loader类加载器
interfaces实现接口
hInvocationHandler

所以我们相应的就要new出这些参数,然后传入Proxy.newProxyInstance方法中,生成一个代理类,然后调用Moveable的move方法完成该功能。
Client.java

package com.xjh.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

import com.xjh.jdkproxy.TimeHandler;

public class Client {

	public static void main(String[] args) {
		Car car = new Car();
		InvocationHandler h = new TimeHandler(car);
		Class<?> cls = car.getClass();
		Moveable m = (Moveable)Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), h);
		m.move();
	}
	
}

执行结果如下:
执行结果
这样我们就不需要编写多个不同类的代理类,只需要在TimeHandler构造函数中传入相应的类就可以了,系统的JDK会自动生成对应的Class。
所谓的Dynamic Proxy是这样一种Class:它是在运行时生成的Class,该Class需要实现一组interface,并且使用动态代理类时,必须要实现InvocationHandler接口。
当然我们需要搞懂动态代理是如何实现的,主要的就是通过Proxy的newProxyInstance方法返回代理对象,但是具体步骤是什么呢?

  1. 声明一段源码(动态产生代理)
  2. 编译源码(JDK Compiler API),产生新的代理类
  3. 将这个类加载到内存中,产生一个新的代理对象
  4. 返回代理对象

接下来我们就根据这个步骤来模仿JDK中动态代理的实现:
首先新建一个Proxy类,里面新建一个newProxyInstance的静态方法,然后根据我们之前的步骤来完成这个动态代理。
Proxy.java

package com.xjh.proxy;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class Proxy {

	public static Object newProxyInstance(Class infce) throws Exception{
		//声明一段源码(动态产生代理)
		String methodStr = "";
		for(Method m : infce.getMethods()) {
			methodStr += "	@Override\r\n" + 
					"	public void " + m.getName() + "() {\r\n" + 
					"		long starttime = System.currentTimeMillis();\r\n" + 
					"		System.out.println(\"汽车开始行驶...\");\r\n" + 
					"		m." + m.getName() + "();\r\n" + 
					"		long endtime = System.currentTimeMillis();\r\n" + 
					"		System.out.println(\"汽车结束行驶... 组合实现方法汽车行驶时间:\" + ( endtime - starttime) + \"ms\");\r\n" + 
					"	}";
		}
		String str = "package com.xjh.proxy;\r\n" + 
				"import java.util.Random;\r\n" + 
				"public class $Proxy0 implements " + infce.getName() + " {\r\n" + 
				"	//使用组合的方法实现代理\r\n" + 
				"	private " + infce.getName() + " m;\r\n" +
				"	public $Proxy0(" + infce.getName() + " m) {\r\n" + 
				"		super();\r\n" + 
				"		this.m=m;\r\n" + 
				"	}\r\n" + 
				methodStr + "\r\n" +
				"}";
		//产生代理类的Java文件
		String filename = System.getProperty("user.dir")+"/bin/com/xjh/proxy/$Proxy0.java";
		File file = new File(filename);
		FileWriter writer;
		try {
			writer = new FileWriter(filename);
			writer.write(str);
			writer.flush();
			writer.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//编译源码(JDK Compiler API),产生新的代理类
		//拿到编译器
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		//文件管理者
		StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
		//获取文件
		Iterable units = fileMgr.getJavaFileObjects(filename);
		//编译任务
		CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
		t.call();
		fileMgr.close();
		//将这个类加载到内存中,产生一个新的代理对象
		ClassLoader cl = ClassLoader.getSystemClassLoader();//得到类加载器
		Class c = cl.loadClass("com.xjh.proxy.$Proxy0");
		
		Constructor ctr = c.getConstructor(infce);
		//返回代理对象
		return ctr.newInstance(new Car());
	}
}

其实在这里面我们遇到了一个问题:找不到jdk的lib目录下tools.jar文件,没法编译。因为它编译文件时,会找到JAVA_HOME的jre\lib\tools.jar,但是tools.jar并不在jre/lib中。所以我们需要在Project Explorer中点击右键,然后依次选择Properties -> Java Build Path -> Libraries,选中JRE System Library,然后点击Edit -> Installed JREs,选中你的jre,然后点击Edit -> Add External JARs,在本地jdk/lib目录下选择tools.jar添加,最后Finish -> Apply -> OK即可。
Client.java

package com.xjh.proxy;

public class Client {

	public static void main(String[] args) {
		try {
			Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class);
			m.move();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

之后我们在Window–>Show View–>Other–>Navigator–>Open,打开Navigator视图我们就能看到我们自动生成的代理类。
Navigator视图
Main函数的执行结果:
执行结果
虽然我们首先了动态加载代理类,但是我们的源代码都是固定好的,业务逻辑是写死的,那怎么才能真正的实现动态呢?
当然就是模仿着JDK中的样式,同样定义一个接口,里面定义invoke方法。
InvocationHandler.java

package com.xjh.proxy;

import java.lang.reflect.Method;

public interface InvocationHandler {

	public void invoke(Object o,Method m);
}

然后我们就和在JDK中一样,new一个类实现这个接口。
TimeHandler.java

package com.xjh.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler {

	 Object target;
	
	public TimeHandler(Object target) {
		super();
		this.target = target;
	}

	@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) + "ms");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

之后我们就要在Proxy.java中进行修改他的传入参数,以及一些代码的变动。我们就要根据传入的InvocationHandler参数来动态的添加方法。
Proxy.java

package com.xjh.proxy;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class Proxy {

	public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception{
		//声明一段源码(动态产生代理)
		String methodStr = "";
		for(Method m:infce.getMethods()) {
			methodStr += " @Override\r\n" + 
			"public void " + m.getName() + "(){\r\n" +
			"	try{\r\n" +
			"	Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");\r\n" +
			"	h.invoke(this, md);\r\n" +
			"	} catch (Exception e) {\r\n" +  
			"			e.printStackTrace();\r\n" + 
			"	}" +
			"}";
		}
		String str = "package com.xjh.proxy;\r\n" +
				"import java.lang.reflect.Method;\r\n" + 
				"import java.util.Random;\r\n" + 
				"import com.xjh.proxy.InvocationHandler;\r\n" + 
				"public class $Proxy0 implements " + infce.getName() + " {\r\n" + 
				"	//使用组合的方法实现代理\r\n" + 
				"	private InvocationHandler h;\r\n" +
				"	public $Proxy0(InvocationHandler h) {\r\n" + 
				"		this.h = h;\r\n" + 
				"	}\r\n" + 
				methodStr + "\r\n" +
				"}";
		//产生代理类的Java文件
		String filename = System.getProperty("user.dir")+"/bin/com/xjh/proxy/$Proxy0.java";
		File file = new File(filename);
		FileWriter writer;
		try {
			writer = new FileWriter(filename);
			writer.write(str);
			writer.flush();
			writer.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//编译源码(JDK Compiler API),产生新的代理类
		//拿到编译器
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		//文件管理者
		StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
		//获取文件
		Iterable units = fileMgr.getJavaFileObjects(filename);
		//编译任务
		CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
		t.call();
		fileMgr.close();
		//将这个类加载到内存中,产生一个新的代理对象
		ClassLoader cl = ClassLoader.getSystemClassLoader();//得到类加载器
		Class c = cl.loadClass("com.xjh.proxy.$Proxy0");
		
		Constructor ctr = c.getConstructor(InvocationHandler.class);
		//返回代理对象
		return ctr.newInstance(h);
	}
}

然后我们在Main函数中new出Handler,传入对象就可以获得动态产生的代理类了。
Client.java

package com.xjh.proxy;

public class Client {

	public static void main(String[] args) {
		try {
			Car car = new Car();
			InvocationHandler h = new TimeHandler(car);
			Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class, h);
			m.move();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

执行结果

总结

我们平常使用代理模式的主要愿意是,我们经常会使用某些类库的某些方法,但是对于源码我们是不能进行修改的,我们就可以使用代理模式来在方法前后增加一些业务逻辑,已达到我们想要的效果。这就是AOP(面向切面编程)。
AOP

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值