软件设计模式之代理模式(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     public void doTask(String taskName);
11 
12 }

接下来是被代理类,这个类实现了代理接口,并执行主要的业务逻辑操作。

 1 package com.lcw.proxy.test;
 2 
 3 import java.util.Random;
 4 
 5 /**
 6  * 
 7  * 业务处理类,处理主要的业务(被代理类)
 8  * 
 9  */
10 public class DealTask implements ITask {
11 
12     @Override
13     public void 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 }

再来是代理类,它同样的实现了代理接口,这样一来代理类和被代理类的对象既被"同化",通过构造器可以对被代理类的对象进行引用,从而达到可以访问被代理类的主要业务方法。

 1 package com.lcw.proxy.test;
 2 
 3 
 4 /**
 5  * 
 6  * 代理类,需要实现和被代理类同一接口
 7  * 利用构造方法引用一个被代理类的对象为己持有
 8  *
 9  */
10 public class ProxyTask implements ITask {
11     
12     private ITask dealTask;
13     
14     public ProxyTask(ITask dealTask){
15         this.dealTask=dealTask;//引用一个被代理类的对象
16     }
17 
18     @Override
19     public void doTask(String taskName) {
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 }

然后来个测试类。

 1 package com.lcw.proxy.test;
 2 
 3 
 4 public class ProxyTest {
 5 
 6     /**
 7      * 测试类
 8      */
 9     public static void 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接口创建自己的调用处理器
2、给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
3、以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
4、以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象

好了,再说下去有的朋友要开始混乱了,直接看实例,整理下思路

依旧是上面的代理接口,这里我们需要创建一个自己的调用处理器(实现InvocationHandler接口)

 1 package com.lcw.proxy.test;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 
 6 /**
 7  * 
 8  * 动态代理类 通过实现 InvocationHandler 接口创建自己的调用处理器
 9  * 
10  */
11 public class JDKProxy implements InvocationHandler {
12 
13     private Object dealTask;
14 
15     public JDKProxy(Object dealTask) {
16         this.dealTask = dealTask;// 引用被代理类的对象
17     }
18 
19     /**
20      * 参数: proxy为代理类的对象 method为被代理类的方法 args为该方法的参数数组
21      * 
22      */
23     @Override
24     public 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 }

接着是测试类

 1 package com.lcw.proxy.test;
 2 
 3 import java.lang.reflect.Proxy;
 4 
 5 public class ProxyTest {
 6 
 7     /**
 8      * 测试类
 9      */
10     public static void main(String[] args) {
11         // //静态代理
12         // ITask dealTask=new DealTask();//获取被代理类的对象
13         // ITask proxyTask=new ProxyTask(dealTask);//获取静态代理类对象
14         // proxyTask.doTask("打喷嚏..");
15 
16         // 动态代理
17         ITask dealTask = new DealTask();// 获取被代理类的对象
18         JDKProxy jdkProxy = new JDKProxy(dealTask);// 获取动态代理类对象
19         /**
20          * loader 类加载器 interfaces 实现接口 h InvocationHandler
21          */
22         ITask iTask = (ITask) Proxy.newProxyInstance(dealTask.getClass()
23                 .getClassLoader(), dealTask.getClass().getInterfaces(),
24                 jdkProxy);
25         iTask.doTask("在发呆...");
26     }
27 
28 }

 看下实现效果:

动态代理的优缺点:
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。在本示例中看不出来,因为invoke方法体内嵌入了具体的外围业务(记录任务处理前后时间并计算时间差),实际中可以类似Spring AOP那样配置外围业务。动态代理的应用使我们的类职责更加单一,复用性更强。

美中不足:
Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface代理的桎梏,因为它的设计注定了这个遗憾。JDK动态代理只能为实现接口的类进行代理,没有实现接口的类无法代理。

有问题就会想去解决,所以此时Cglib要登场了。

4、Cglib动态代理:

Cglib的介绍:

代理为控制要访问的目标对象提供了一种途径。当访问对象时,它引入了一个间接的层。JDK自从1.3版本开始,就引入了动态代理,并且经常被用来动态地创建代理。JDK的动态代理用起来非常简单,但它有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的继承的类,该怎么办?现在我们可以使用Cglib包。

Cglib是针对类来实现代理的,实现原理是为被代理类产生一个子类,通过拦截技术拦截父类方法的调用。

来看个小例子:

首先是一个业务逻辑类

 1 package com.lcw.proxy.test;
 2 
 3 import java.util.Random;
 4 
 5 public class Task {
 6     
 7     public void doTask(){
 8         System.out.println("我在完成任务");
 9         try {
10             Thread.sleep(new Random().nextInt(1000));
11         } catch (InterruptedException e) {
12             e.printStackTrace();
13         }
14     }
15 
16 }

再来一个Cglib代理类,提供代理对象,这里需要实现MethodInterceptor接口

 1 package com.lcw.proxy.test;
 2 
 3 import java.lang.reflect.Method;
 4 
 5 import net.sf.cglib.proxy.Enhancer;
 6 import net.sf.cglib.proxy.MethodInterceptor;
 7 import net.sf.cglib.proxy.MethodProxy;
 8 
 9 /**
10  * 
11  * 创建Cglib的代理类(需要实现MethodInterceptor接口)
12  *
13  */
14 public class CglibProxy implements MethodInterceptor{
15     
16     //需要向客户端返回一个代理类对象
17     private Enhancer enhancer=new  Enhancer();
18     
19     public Object getProxy(Class c){
20         //cglib代理的原理就是为被代理类(父类)创建子类
21         enhancer.setSuperclass(c);
22         enhancer.setCallback(this);
23         return enhancer.create();
24     }
25 
26     /**
27      * 参数:
28      * obj被代理类的对象
29      * method被代理类的方法
30      * args被代理类方法的参数
31      * proxy代理类对象
32      */
33     @Override
34     public Object intercept(Object obj, Method method, Object[] args,
35             MethodProxy proxy) throws Throwable {
36         long startTime=System.currentTimeMillis();//记录业务起始时间
37         proxy.invokeSuper(obj, args);//cglib代理的原理就是为被代理类(父类)创建子类,所以这边调用了父类super,参数一:父类的对象、参数二:方法参数
38         long endTime=System.currentTimeMillis();//记录业务结束时间
39         System.out.println("本次任务执行时间为:"+(endTime-startTime+"毫秒"));
40         return null;
41     }
42 
43 }

写个测试类试试

 1 package com.lcw.proxy.test;
 2 
 3 import java.lang.reflect.Proxy;
 4 
 5 public class ProxyTest {
 6 
 7     /**
 8      * 测试类
 9      */
10     public static void main(String[] args) {
11         // //静态代理
12         // ITask dealTask=new DealTask();//获取被代理类的对象
13         // ITask proxyTask=new ProxyTask(dealTask);//获取静态代理类对象
14         // proxyTask.doTask("打喷嚏..");
15 
16         // 动态代理
17 //        ITask dealTask = new DealTask();// 获取被代理类的对象
18 //        JDKProxy jdkProxy = new JDKProxy(dealTask);// 获取动态代理类对象
19 //        /**
20 //         * loader 类加载器 interfaces 实现接口 h InvocationHandler
21 //         */
22 //        ITask iTask = (ITask) Proxy.newProxyInstance(dealTask.getClass()
23 //                .getClassLoader(), dealTask.getClass().getInterfaces(),
24 //                jdkProxy);
25 //        iTask.doTask("在发呆...");
26         
27         //Cglib代理
28         CglibProxy cglibProxy=new CglibProxy();
29         Task task=(Task) cglibProxy.getProxy(Task.class);
30         task.doTask();
31         
32     }
33 
34 }

运行效果:

作者:Balla_兔子
出处:http://www.cnblogs.com/lichenwei/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
正在看本人博客的这位童鞋,我看你气度不凡,谈吐间隐隐有王者之气,日后必有一番作为!旁边有“推荐”二字,你就顺手把它点了吧,相得准,我分文不收;相不准,你也好回来找我!

原文地址:https://www.cnblogs.com/lichenwei/p/4200992.html