软件设计模式之代理模式(JAVA)
貌似停笔了近半个月了,实在不该啊,新的一年,时刻让自己归零。 Back To Zero,就从这篇文章拉开今年的序幕吧。 ? 这篇文章准备介绍下有关代理模式的基本概念和静态代理、动态代理的优缺点及使用方法(包括扩展包CGLIB) ? 代理模式,又称委托模式,顾名思义委托某物去办某事。 举个生活中的例子,临近大年了,在外地学习工作的小伙伴们也都开始购买回家的火车票,先不说网上订票,靠近火车站的小伙伴们自然很方便的可以到车站里去直接购票,那么如果是住郊外远离火车站的小伙伴们那该怎么办呢?当然也是可以专门搭车到火车站去买票,不过实为麻烦,这时候就衍生出一个东西——火车票代售处,没错,有了它,让我们这些远离火车站的小伙伴们也可以很轻松的购买到火车票。 这里的火车票代售其实就是一种代理(委托),火车票代售处代理了火车站售票,然而我们就可以在其中做一些我们想要的操作,比如权限的控制,代售处是不具备有退票的权限的。 ? 好了,言归正传,先来看看代理的概念 1、代理概念? 来一张UML图: 从图中可以看出,代理接口(Subject)、代理类(ProxySubject)、被代理类(RealSubject)形成一个"品"字结构。 代理类和被代理类都同时实现了代理接口,然后代理类通过引用被代理类的对象,来处理用户的访问请求。 这边根据代理类的生成时间又分成了2种代理,一种是静态代理,一种是动态代理。 文绉绉的理论概念让人看的难受,下面直接上实例,帮助大家理解。 ? 2、静态代理 由开发人员创建或某工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,在代理类和被代理类运行前就确定了。? 这里模拟一个计时器操作,记录处理业务的时间,下面直接上代码: ? 首先是代理接口,负责声明业务方法,代理类和被代理类都必须实现它。 1 package com.lcw.proxy.test; 2 3 /** 4 * 5 * 业务接口(代理接口) 6 * 7 */ 8 public interface ITask { 9 10 void doTask(String taskName); 11 12 } 接下来是被代理类,这个类实现了代理接口,并执行主要的业务逻辑操作。 import java.util.Random; 4 7 * 业务处理类,处理主要的业务(被代理类) 9 10 class DealTask implements12 @Override 13 doTask(String taskName) { 14 try { 15 System.out.println("执行任务:" + taskName); 16 Thread.sleep(new Random().nextInt(1000));// 随机休眠一段时间 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 } 21 22 } 再来是代理类,它同样的实现了代理接口,这样一来代理类和被代理类的对象既被"同化",通过构造器可以对被代理类的对象进行引用,从而达到可以访问被代理类的主要业务方法。 3 * 代理类,需要实现和被代理类同一接口 * 利用构造方法引用一个被代理类的对象为己持有 class ProxyTask 11 12 private ITask dealTask; 13 14 public ProxyTask(ITask dealTask){ 15 this.dealTask=dealTask;引用一个被代理类的对象 16 17 19 20 21 long startTime=System.currentTimeMillis();记录业务起始时间 22 dealTask.doTask(taskName);执行被代理类的主要业务 23 long endTime=System.currentTimeMillis();记录业务结束时间 24 System.out.println("本次任务执行时间为:"+(endTime-startTime+"毫秒")); 25 26 27 28 } 然后来个测试类。 class ProxyTest { 5 6 * 测试类 8 9 static main(String[] args) { 10 静态代理 11 ITask dealTask=new DealTask();获取被代理类的对象 12 ITask proxyTask=new ProxyTask(dealTask);//将被代理类的对象注入,获取静态代理类对象 13 proxyTask.doTask("打喷嚏.."); 14 15 16 } 看下运行效果: ? 到这里,静态代理已经实现了,先来看看静态代理的优点, 被代理类只需要去关注主要的业务实现,其余操作比如日志记录,计时,权限控制等都可以交给代理类去额外的处理。 不过,这样子的实现方式,真的没有问题吗? 假如我们现在要为很多个业务方法都实现计时功能,如果这些方法分布在不同的类,那么是不是要为每一个类再单独的去创建代理类,长久下来,类体积会膨胀会爆炸的。 我们退一步想,假如我们现在额外的要扩展业务,在代理接口里添加新的业务方法,那么除了被代理类要去实现这个方法以外,是不是所有的代理类也都要去额外的添加实现这个方法,所谓的"牵一发而动全身",需要额外的添加大量的重复代码,这是违背软件设计原则的。 还有就是代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。 为解决这个问题,我们的动态代理就要出场了。 ? 3、JDK动态代理 JDK动态代理类的源码是在程序运行期间由虚拟机JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。在代理类和被代理类程序运行时确定。 在Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy类的支持。 来看下Java API里的主要方法介绍(具体自己去翻阅哈) ? java.lang.reflect.Proxy java.lang.reflect.InvocationHandler? 这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对被代理类的代理访问。 ? 动态代理实现步骤 1、实现InvocationHandler接口创建自己的调用处理器 ? 好了,再说下去有的朋友要开始混乱了,直接看实例,整理下思路 依旧是上面的代理接口,这里我们需要创建一个自己的调用处理器(实现InvocationHandler接口) java.lang.reflect.InvocationHandler; java.lang.reflect.Method; * 动态代理类 通过实现 InvocationHandler 接口创建自己的调用处理器 9 10 11 class JDKProxy InvocationHandler { 12 Object dealTask; 14 15 JDKProxy(Object dealTask) { 16 this.dealTask = dealTask; 引用被代理类的对象 17 18 * 参数: proxy为代理类的对象 method为被代理类的方法 args为该方法的参数数组 21 * 22 23 24 Object invoke(Object proxy,Method method,Object[] args) 25 throws Throwable { 26 调用实现业务逻辑方法,并实现附属功能(日志,权限判断等) 27 利用反射机制将请求分派给被代理类处理 28 29 long startTime = System.currentTimeMillis(); 记录业务起始时间 30 invoke方法参数sub是实际的被代理对象,args为执行被代理对象相应操作所需的参数 31 method.invoke(dealTask,args); 返回值是一个Object对象也是调用被代理类方法的返回值(这里只是实现计时操作,所以返回值为null) 32 long endTime = System.currentTimeMillis(); 记录业务结束时间 33 System.out.println("本次任务执行时间为:" + (endTime - startTime + "毫秒"34 return null; 35 36 37 } 接着是测试类 java.lang.reflect.Proxy; 6 7 9 11 // 静态代理 12 ITask dealTask=new DealTask();获取被代理类的对象 13 ITask proxyTask=new ProxyTask(dealTask);获取静态代理类对象 proxyTask.doTask("打喷嚏.."); 15 动态代理 17 ITask dealTask = 获取被代理类的对象 18 JDKProxy jdkProxy = new JDKProxy(dealTask); 获取动态代理类对象 19 * loader 类加载器 interfaces 实现接口 h InvocationHandler 21 22 ITask iTask = (ITask) Proxy.newProxyInstance(dealTask.getClass() .getClassLoader(),dealTask.getClass().getInterfaces(),1)">24 jdkProxy); 25 iTask.doTask("在发呆..."28 } ?看下实现效果: ? 动态代理的优缺点: 美中不足: 有问题就会想去解决,所以此时Cglib要登场了。 ? 4、Cglib动态代理: Cglib的介绍: 代理为控制要访问的目标对象提供了一种途径。当访问对象时,它引入了一个间接的层。JDK自从1.3版本开始,就引入了动态代理,并且经常被用来动态地创建代理。JDK的动态代理用起来非常简单,但它有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的继承的类,该怎么办?现在我们可以使用Cglib包。 Cglib是针对类来实现代理的,实现原理是为被代理类产生一个子类,通过拦截技术拦截父类方法的调用。 来看个小例子: 首先是一个业务逻辑类 Task { 6 doTask(){ 8 System.out.println("我在完成任务" 9 10 Thread.sleep(new Random().nextInt(100011 } 13 14 16 } 再来一个Cglib代理类,提供代理对象,这里需要实现MethodInterceptor接口 net.sf.cglib.proxy.Enhancer; net.sf.cglib.proxy.MethodInterceptor; net.sf.cglib.proxy.MethodProxy; 8 * 创建Cglib的代理类(需要实现MethodInterceptor接口) 13 class CglibProxy MethodInterceptor{ 15 16 需要向客户端返回一个代理类对象 17 private Enhancer enhancer=new Enhancer(); 18 Object getProxy(Class c){ 20 cglib代理的原理就是为被代理类(父类)创建子类 enhancer.setSuperclass(c); 22 enhancer.setCallback(thisreturn enhancer.create(); 25 26 27 * 参数: 28 * obj被代理类的对象 29 * method被代理类的方法 30 * args被代理类方法的参数 31 * proxy代理类对象 32 33 34 Object intercept(Object obj,Object[] args,1)">35 MethodProxy proxy) 36 37 proxy.invokeSuper(obj,1)">cglib代理的原理就是为被代理类(父类)创建子类,所以这边调用了父类super,参数一:父类的对象、参数二:方法参数 38 39 System.out.println("本次任务执行时间为:"+(endTime-startTime+"毫秒"40 41 42 43 } 写个测试类试试 动态代理 ITask dealTask = new DealTask(); 获取被代理类的对象 JDKProxy jdkProxy = new JDKProxy(dealTask); 获取动态代理类对象 /** */ 22 ITask iTask = (ITask) Proxy.newProxyInstance(dealTask.getClass() 25 iTask.doTask("在发呆..."); 26 Cglib代理 28 CglibProxy cglibProxy= CglibProxy(); 29 Task task=(Task) cglibProxy.getProxy(Task. task.doTask(); 31 32 33 34 } 运行效果: ? 作者:Balla_兔子 (编辑:北几岛) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |