设计模式之代理模式
2016-05-26 15:20
441 查看
1.代理模式简介(Proxy)
1.1代理模式定义
为其他对象提供一种代理以控制对这个对象的访问,代理对象起到中介作用,可去掉功能或者增加额外的服务。1.2代理模式的分类
远程代理:为不同的地理对象提供局域网代表对象
虚拟代理:
根据需要将资源消耗很大的对象进行延迟加载,真正需要的时候再进行创建。(如访问网页时图片的加载,可先用别的图片进行代替)
保护代理:
控制对一个对象的访问权限。(如论坛游客不能发帖等)
智能引用代理:
提供对目标对象额外的服务。(如Spring AOP模式)
2.智能引用代理案例
只能引用代理是在实际实践中使用醉倒的一种代理。智能引用代理可以分为静态代理和动态代理
2.1 静态代理
代理和被代理对象在代理开始之前就是确定的。他们都实现相同的接口或者继承相同的抽象类。下面我们通过一个具体的案例来看一下。
假设有一辆车,我们要让他行驶,停下来,并记录它的行驶时间。可以用以下模式
2.1.1普通的实现形式
首先新建一个Movable接口public interface Movable { void move(); }
建立一个Car类继承Movable接口并实现move()方法
import java.util.Random; public class Car implements Movable { @Override public void move() { // 记录一下汽车行驶的开始时间 long starttime = System.currentTimeMillis();// System.currentTimeMillis();获取系统当前时间 System.out.println("汽车开始行驶.........."); // 实现开车 try { Thread.sleep(new Random().nextInt(1000)); System.out.println("汽车行驶中、、、、、、、"); } catch (InterruptedException e) { e.printStackTrace(); } // 记录一下汽车行驶的终止时间 long endtime = System.currentTimeMillis(); System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒"); } }
在测试方法中进行测试
public class Test { public static void main(String[] args) { Car car = new Car(); car.move(); } }
测试结果为
汽车开始行驶.......... 汽车行驶中、、、、、、、 汽车停止行驶,行驶时间为:127毫秒
下面是一个静态代理的两种方式:
2.1.2继承方式实现代理
修改Car中的move()方法,使其只实现行驶功能,开始实行和行驶完成功能在子类中进行实现import java.util.Random; public class Car implements Movable { @Override public void move() { // 实现开车 try { Thread.sleep(new Random().nextInt(1000)); System.out.println("汽车行驶中、、、、、、、"); } catch (InterruptedException e) { e.printStackTrace(); } } }
新建一个子类Car2继承Car并重写move()方法
/** * 继承的方式代理Car * * @author Administrator * */ public class Car2 extends Car { @Override public void move() { // 记录一下汽车行驶的开始时间 long starttime = System.currentTimeMillis(); System.out.println("汽车开始行驶.........."); super.move(); // 记录一下汽车行驶的终止时间 long endtime = System.currentTimeMillis(); System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒"); } }
在测试方法中测试
public class Test { public static void main(String[] args) { Car car2 = new Car2(); car2.move(); } }
这种方式为继承方式实现代理。
2.1.3 聚合的方式实现代理
新建一个car3继承movable接口,并实现move()方法。在类中添加一个Car类型的全局变量,并添加带有Car的构造方法。
在move()方法中实现Car中的move方法。
/** * 聚合的方式实现代理 * * @author Administrator * */ public class Car3 implements Movable { private Car car; public Car3(Car car) { super(); this.car = car; } @Override public void move() { // 记录一下汽车行驶的开始时间 long starttime = System.currentTimeMillis(); System.out.println("汽车开始行驶.........."); car.move(); // 记录一下汽车行驶的终止时间 long endtime = System.currentTimeMillis(); System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒"); } }
在测试类中测试
public class Test { public static void main(String[] args) { Car car = new Car(); Movable car3 = new Car3(car); car3.move(); } }
这种就是通过聚合的方式实现代理。
2.1.4 继承方式和聚合方式的对比
如果希望实现类功能的叠加。先记录日志,再记录时间。需要新建一个Car4继承Car,先实现日志的处理再计算运行时间。
如果希望先记录时间,再记录日志的处理,就需要新建一个Car5,先对时间处理,再对日志处理。
如果想实现先对权限的判断,再做日志的处理,再做时间的处理,只能新建一个Car6。
如果通过继承的方式对类进行代理,当功能增加或者改变时,只能通过增加类的方式进行。类只会无限膨胀下去。因此一般不推荐继承的方式代理。
以下为一个聚合代理方式,先记录日志再记录时间
新建一个日志代理类,实现movab接口
/** * 对时间的代理 * @author Administrator * */ public class CarTimeProxy implements Movable { private Movable movable;//这次不是Car属性,而是他们共同的接口 public CarTimeProxy(Movable movable) { super(); this.movable = movable; } @Override public void move() { // 记录一下汽车行驶的开始时间 long starttime = System.currentTimeMillis(); System.out.println("汽车开始行驶.........."); movable.move(); // 记录一下汽车行驶的终止时间 long endtime = System.currentTimeMillis(); System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒"); } }
再编写一个日志的代理类,同样实现movable接口
/** * 对日志的代理 * @author Administrator * */ public class CarLogProxy implements Movable { private Movable movable; public CarLogProxy(Movable movable) { super(); this.movable = movable; } @Override public void move() { System.out.println("日志开始.........."); movable.move(); System.out.println("日志结束"); } }
在测试类中,我们实现先对日志记录,再对时间记录
public class Test2 { public static void main(String[] args) { Car car = new Car(); CarTimeProxy carTimeProxy = new CarTimeProxy(car); CarLogProxy carLogProxy = new CarLogProxy(carTimeProxy); carLogProxy.move(); } }
得到的结果如下
日志开始.......... 汽车开始行驶.......... 汽车行驶中、、、、、、、 汽车停止行驶,行驶时间为:4毫秒 日志结束
如果想先记录汽车行驶的时间,就先创建CarLogProxy类,再创建CarTimeProxy类,并在CarTimeProxy类中创建CarLogProxy属性。
public class Test2 { public static void main(String[] args) { Car car = new Car(); CarLogProxy carLogProxy = new CarLogProxy(car); CarTimeProxy carTimeProxy = new CarTimeProxy(carLogProxy); carTimeProxy.move(); } }
此时控制台打印输出的结果为:
汽车开始行驶.......... 日志开始.......... 汽车行驶中、、、、、、、 日志结束 汽车停止行驶,行驶时间为:919毫秒
2.2 动态代理
此时实现的代理类只是对car的代理,如果再来一辆货车,还要实现TrainLogProxy 和TrainTimeProxy 两个类. 假如有100个类要做事件代理。就需要新建一百个代理类。因此可以使用动态代理类,实现对不同类不同方法的代理。
动态代理的主要机制就是在类和代理类之间加入一个ProxyHandler(事务处理器)这样一个类
Java动态代理类位于java.lang.reflect包下,一般涉及到以下两个类。
(1)Interface InvocationHandler:该接口仅定义了一个方法:
public Object invoke(Object obj,Method method, Object args[])
在实际使用中,第一个参数obj一般指代理类,method是指被代理的方法,args为该方法的参数数组。这个抽象方法在代理类中动态实现。
(2) Proxy:该类极为动态代理类
static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
返回代理类的一个实例,返回后的代理类可以被当做代理类使用(可使用被代理类的在接口中声明过的方法)
我们可以通过一个实例来看一下
2.2.1 动态代理实例
首先新建一个TimeHandler(事件代理器)类,实现InvocationHandler接口。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; } /** * proxy:被代理的对象 * method:被代理对象的方法 * args 方法的参数 * * 返回值 * Object方法的返回值 */ @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) + "毫秒"); return null;//这里没有返回值 } }
然后就可以在测试类里面测试
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { Car car = new Car(); InvocationHandler h = new TimeHandler(car); Class<?> cls = car.getClass(); /** * loader 类加载器 * interfaces 实现接口 * h InvocationHandler */ Movable m = (Movable) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), h); m.move(); } }
这样就实现了对Car类的动态代理。
2.2.2 动态代理介绍
所谓Dynamic Proxy是这样一种class:它是在运行时生成的class
该class需要实现一组interface
使用动态代理类时,必须实现InvocationHandler接口。
2.2.3 动态代理实现的步骤
1、创建一个实现接口InvocationHandler的类,必须实现invoke方法2、创建被代理的类以及接口
3、调用Proxy的静态方法,创建一个代理类。
Proxy.newProxyInstance(loader, interfaces, h)
4、调用代理类的方法。
2.2.4 CGLib动态代理
JDK动态代理:1、只能代理实现了接口的类
2、没有实现接口的类不能实现JDK的动态代理
CGLIB动态代理:
1、针对类来实现代理的
2、对制定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。
下面是CGLIB创建动态代理的一个实例:
首先在项目里面引入CGlib的jar包。jar包下载地:
http://download.csdn.net/download/beidiqiuren/9533454
我们先建立一个火车类,不实现任何接口,并添加一个move方法
public class Train { public void move(){ System.out.println("火车正在行驶"); } }
其次,添加一个动态代理类。
import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CglibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){ //设置创建子类的类(被代理的类) enhancer.setSuperclass(clazz); //回调 enhancer.setCallback(this); return enhancer.create(); } /** * 拦截所有目标类方法的调用 * obj 目标类的实例 * method 目标方法的反射对象 * args 方法的参数 * proxy 代理类的实例 */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("日志开始。。。。"); //代理类调用父类的方法 proxy.invokeSuper(obj, args); System.out.println("日志结束。。。。"); return null; } }
这里之所以调用父类的代理方法是因为:intercept()方法拦截所有目标类方法的调用,如果调用proxy.invoke(obj, args);
会报java.lang.StackOverflowError异常,原因是将代理类作为目标类,这样会无限循环调用intercept方法,
导致无限死循环;二代理类的父类只有一个,调用父类的方法只会调用一次。
我们在测试类中测试一下
public class Test { public static void main(String[] args) { CglibProxy proxy = new CglibProxy(); Train t = (Train) proxy.getProxy(Train.class); t.move(); } }
测试结果为
日志开始。。。。 火车正在行驶 日志结束。。。。
可见代理已经成功。
3.模拟动态代理实现思路
动态代理实现思路实现功能:通过Proxy的newProxyInstance返回代理对象
1、声明一段源码(动态产生代理)
2、编译源码(JDK Compiler API),产生新的类(代理类)
3、将这个类load到内存当中,产生一个新的对象(代理对象)
4、return 代理对象
下面我们来实际编写一下
3.1 声明一段源码
在此,我们使用一个外部的jar包简化上传下载的代码jar包地址:http://download.csdn.net/download/beidiqiuren/9533455
package proxy; import java.io.File; import org.apache.commons.io.FileUtils; public class Proxy { public static Object newProxyInstance() throws Exception { String rt = "\r\n"; String str = "package proxy;" + rt + "public class CarTimeProxy implements Movable {" + rt + " private Movable movable;" + rt + "public CarTimeProxy(Movable movable) {" + rt + " super();" + rt + " this.movable = movable;" + rt + " }" + rt + " @Override" + rt + " public void move() {" + rt + " long starttime = System.currentTimeMillis();" + rt + " System.out.println(\"汽车开始行驶..........\");" + rt + " movable.move();" + rt + " long endtime = System.currentTimeMillis();" + rt + " System.out.println(\"汽车停止行驶,行驶时间为:\" + (endtime - starttime) + \"毫秒\");" + rt + " }" + rt + "}"; // 编译:先生成一个java文件,对这个文件进行编译 // System.getProperty("user.dir")当前路径 String filename = System.getProperty("user.dir") + "/bin/proxy/$Proxy.java"; File file = new File(filename); //FileUtils这个类可以快速的对文件进行删除和读写 FileUtils.writeStringToFile(file, str); return null; } }
为了方便起见,代理类我们就使用之前编写好的CarTimeProxy
在测试类中运行
package proxy; public class Test3 { public static void main(String[] args) throws Exception { Proxy.newProxyInstance(); } }
运行之后,生成了一个新的java文件
接下来,我们进一步优化这个代理类
将代理类实现的接口实现动态调整,并通过动态获得应重写的方法。
3.2 优化一段源码
package proxy; import java.io.File; import java.lang.reflect.Method; import org.apache.commons.io.FileUtils; public class Proxy { public static Object newProxyInstance(Class Infce) throws Exception { String rt = "\r\n"; String methodStr = ""; for(Method m:Infce.getMethods()){ methodStr +=" @Override" + rt + " public void "+m.getName()+"() {" + rt + " long starttime = System.currentTimeMillis();" + rt + " System.out.println(\"汽车开始行驶..........\");" + rt + " movable."+m.getName()+"();" + rt + " long endtime = System.currentTimeMillis();" + rt + " System.out.println(\"汽车停止行驶,行驶时间为:\" + (endtime - starttime) + \"毫秒\");" + rt + " }" ; } String str = "package proxy;" + rt + "public class $Proxy0 implements "+Infce.getName()+" {" + rt + " private "+Infce.getName()+" movable;" + rt + "public $Proxy0("+Infce.getName()+" movable) {" + rt + " super();" + rt + " this.movable = movable;" + rt + " }" + rt + methodStr+rt + "}"; // 编译:先生成一个java文件,对这个文件进行编译 // System.getProperty("user.dir")当前路径 String filename = System.getProperty("user.dir") + "/bin/proxy/$Proxy0.java"; File file = new File(filename); //FileUtils这个类可以快速的对文件进行删除和读写 FileUtils.writeStringToFile(file, str); return null; } }
在测试类中运行,生成的$Proxy0.java文件并无不妥的地方,我们开始对这个文件进行编译。
3.3 编译源码
在Proxy类的newProxyInstance()中。我们对生成的文件进行编译//得到当前系统的编译器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); //创建文件的管理者 StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); //根据文件名得到管理文件的数组 Iterable units = fileMgr.getJavaFileObjects(filename); //编译任务 CompilationTask task = compiler.getTask(null, fileMgr, null, null, null, units); //进行编译 task.call(); fileMgr.close();
在测试类中运行
编译后,在bin目录下生成了一个$Proxy0.class文件。接下来就是把这个文件载入到内存当中。
3.4 将编译后的文件载入到内存当中
//load到内存当中 ClassLoader cl = ClassLoader.getSystemClassLoader(); Class c = cl.loadClass("proxy.$Proxy0"); //根据构造器初始化创建代理类 //构造器传递的参数就是接口的名字 Constructor ctr = c.getConstructor(Infce); //根据构造器创建对象 return ctr.newInstance(new Car());
在测试类中测试
package proxy; public class Test3 { public static void main(String[] args) throws Exception { Movable movable = (Movable) Proxy.newProxyInstance(Movable.class); movable.move(); } }
此时控制台打印输出的结果为
汽车开始行驶.......... 汽车行驶中、、、、、、、 汽车停止行驶,行驶时间为:126毫秒
代理成功。
3.5 动态指定业务逻辑
在上面的案例中,业务逻辑需要代理的方法是写死在proxy类中的,而且需要传入Car这个类,如何动态的制定业务逻辑?下面我们模拟一下真实的JDK Proxy
首先,新建一个InvocationHandler接口
package proxy; import java.lang.reflect.Method; public interface InvocationHandler { public void invoke(Object o,Method m); }
定义一个TimeHandler类实现InvocationHandler接口。
package proxy; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class TimeHandler implements InvocationHandler { private Object target; public TimeHandler(Object target) { super(); this.target = target; } @Override public void invoke(Object o, Method m) { // TODO Auto-generated method stub try { long starttime = System.currentTimeMillis(); System.out.println("汽车开始行驶.........."); m.invoke(target); long endtime = System.currentTimeMillis(); System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
然后我们修改Proxy类,使其更加独立。
package proxy; import java.io.File; 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; import org.apache.commons.io.FileUtils; public class Proxy { public static Object newProxyInstance(Class Infce,InvocationHandler h) throws Exception { String rt = "\r\n"; String methodStr = ""; for(Method m:Infce.getMethods()){ methodStr +=" @Override" + rt + " public void "+m.getName()+"() {" + rt+ "try{"+ " Method md = "+Infce.getName()+".class.getMethod(\""+ m.getName()+"\");"+rt + "h.invoke(this,md);"+rt + "}catch(Exception e){e.printStackTrace();}"+ " }" ; } String str = "package proxy;" + rt +"import proxy.InvocationHandler;"+rt+ "import java.lang.reflect.Method;"+rt+ "public class $Proxy0 implements "+Infce.getName()+" {" + rt + "private InvocationHandler h;"+rt+ "public $Proxy0(InvocationHandler h) {" + rt + " super();" + rt + " this.h = h;" + rt + " }" + rt + methodStr+rt + "}"; // 编译:先生成一个java文件,对这个文件进行编译 // System.getProperty("user.dir")当前路径 String filename = System.getProperty("user.dir") + "/bin/proxy/$Proxy0.java"; File file = new File(filename); //FileUtils这个类可以快速的对文件进行删除和读写 FileUtils.writeStringToFile(file, str); //得到当前系统的编译器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); //创建文件的管理者 StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); //根据文件名得到管理文件的数组 Iterable units = fileMgr.getJavaFileObjects(filename); //编译任务 CompilationTask task = compiler.getTask(null, fileMgr, null, null, null, units); //进行编译 task.call(); fileMgr.close(); //load到内存当中 ClassLoader cl = ClassLoader.getSystemClassLoader(); Class c = cl.loadClass("proxy.$Proxy0"); //根据构造器初始化创建代理类 //构造器传递的参数就是接口的名字 Constructor ctr = c.getConstructor(InvocationHandler.class); //根据构造器创建对象 return ctr.newInstance(h); } }
最后在测试类中测试:
package proxy; public class Test3 { public static void main(String[] args) throws Exception { Car car = new Car(); InvocationHandler h = new TimeHandler(car); Movable movable = (Movable) Proxy.newProxyInstance(Movable.class,h); movable.move(); } }
成功。
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- c++11 + SDL2 + ffmpeg +OpenAL + java = Android播放器
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序