zoukankan      html  css  js  c++  java
  • 代理模式

    版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习!

          有这样一种场景,有一个实现类实现了某种功能,这个实现类我们无法修改或者不允许被修改,但是除了实现类里的功能我们还需要扩展其他的功能,这种情况时我们该怎么办呢?我们可以创建一个代理类,在代理类里调用实现类里的功能并且在代理类中扩展我们需要的功能,客户端直接调用代理类而不需要关心实现类,这就是代理模式的思想。简单来说,代理模式的本质就是创建一个代理,代理类在原有类的行为基础上再加上一个额外的行为,甚至是替换掉原有类的行为,返回一个代理供客户端调用。

          举一个生活中常见的例子,我们平时去房产中介公司租房,房产中介提供出租房子的功能,但是房产中介本身并没有房子,房子是房东委托给中介公司,授权给他出租,租房者不需要关心房东的信息或者说无法获得房东的信息,只需要直接向房产中介获取租房信息。可能这个例子不是很恰当,但是它反映了代理模式的思想。房东具有出租房屋的功能,房东直接跟中介交互并把这种功能委托给中介,因此中介具有了出租房屋的功能,并且额外加入收取中介费的功能,租房者直接跟中介交互通过中介获得房东出租房屋的功能。

          代理模式中有以下几个角色:

          1)抽象角色:声明真实对象和代理对象的共同接口;

          2)代理角色:代理角色内部有对真实角色的引用,因此可以操作真实对象的功能;代理对象和真实对象实现相同的接口,以便在任何时候都可以代替真实对象;代理对象可以在执行真实对象的操作时,加上一些额外的操作,相当于对真实对象进行封装;

          3)真实角色:代理角色所代表的对象,是我们最终要操作的对象。

          在上面的举例中,抽象角色就是出租房屋这一功能,房东就相当于真实角色,实现了抽象角色这一接口,具有了出租房屋的功能,房产中介就相当于代理角色,他引用了真实角色出租房屋的功能,并且额外加上了收取中介费的功能。

          代理模式可以分为两种:静态代理模式动态代理模式。这两种代理模式本质上是一样的,只是生成代理类的方式不一样。

          1、静态代理模式

          静态代理模式采用的方式是手动引入真实角色的引用,这种方式将被代理的对象定义死了,因此静态代理适合被代理对象很固定、只需要去代理一个类或者若干个固定的类并且数量不是很多的场景。代码示例:

          定义抽象角色:      

    1 /**
    2  * 抽象角色:
    3  * 定义一个接口或者虚拟类
    4  * 
    5  */
    6 public interface Subject {
    7     
    8       public void rentHouse();
    9 }

         

          定义真实角色:     

     1 /**
     2  * 真实角色:
     3  * 实现抽象角色接口
     4  */
     5 public class RealSubject implements Subject {
     6 
     7     public void rentHouse() {
     8         
     9         System.out.println("房东:出租房屋");
    10     }
    11 
    12 }

          定义代理角色:      

     1 /**
     2  * 代理角色:
     3  * 1、实现抽象角色接口;
     4  * 2、维护一个真实对象的引用,用来操作真实对象;
     5  * 3、额外添加功能;
     6  *
     7  */
     8 
     9 public class ProxySubject implements Subject{
    10     
    11     private RealSubject realSubject; //代理角色内部引用了真实角色
    12 
    13     public void rentHouse() {
    14         
    15         if(null == realSubject){
    16             realSubject = new RealSubject();
    17         }
    18         
    19         realSubject.rentHouse();//执行真实角色所完成的事情
    20         
    21         this.getFee();//额外加入自己的功能
    22         
    23     }
    24     
    25     private void getFee(){
    26         System.out.println("代理角色:收取中介费");
    27     }
    28     
    29 }

          客户端调用:    

     1 public class Client {
     2     
     3     public static void main(String[] args) {
     4         
     5         Subject subject = new ProxySubject();//获得代理对象,由代理对象来执行所需的操作
     6         
     7         subject.rentHouse();//输出结果:房东:出租房屋
     8                             //         代理角色:收取中介费
     9         
    10     }
    11 
    12 }

           静态代理在使用场景上有一定的局限性,因为代理类中引入的对象是写死的(比如上面的RealSubject),因此它只能代理固定的一个类或若干个类。当需要代理一系列类中的某些方法,比如比较典型的应用就是springAOP,我们需要创建出一批代理类来代理一系列类中的某些方法,这个时候静态代理就很难满足我们的需求了,我们需要用动态代理的方式。

          2、动态代理

          动态代理是JDK自带的功能,它涉及到一个接口InvocationHandler和一个类Proxy,这两个类都在java.lang.reflect包下。我们需要实现InvocationHandler接口,并且调用Proxy类的静态方法newProxyInstance方法来获得一个代理类的实例。动态代理的实现过程与静态代理最大的区别就是代理实例是动态生成的。我们先来看看实现过程:

          定义抽象角色:      

    1 /**
    2  * 抽象角色:
    3  * 定义一个接口
    4  * 
    5  */
    6 public interface Subject {
    7     
    8       public void rentHouse();
    9 }

          定义真实角色:   

     1 /**
     2  * 真实角色:
     3  * 实现抽象角色接口
     4  */
     5 public class RealSubject implements Subject {
     6 
     7     public void rentHouse() {
     8         
     9         System.out.println("房东:出租房屋");
    10     }
    11 
    12 }

          定义动态代理角色:     

     1 /**
     2  * 动态代理角色:
     3  * 
     4  * 该代理类的内部属性是Object类型,实际使用的时候通过该类的构造方法传入一个对象,
     5  * 此外,该类还实现了invoke方法,该方法中的method.invoke其实就是调用被代理对象
     6  * 的将要被执行的方法,参数sub表示执行的方法从属于sub,在动态代理类中我们还可以
     7  * 在执行真实对象的方法之外再额外加入一些自己的方法
     8  *
     9  */
    10 public class DynamicProxySubject implements InvocationHandler {
    11     
    12     //被代理类对象,声明为Object类型,这样就可以传入任何类型的对象
    13     private Object sub; 
    14     
    15     public DynamicProxySubject(Object obj){
    16         this.sub = obj;
    17     }
    18 
    19     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    20 
    21         System.out.println("before calling :"+ method); //可以在执行真实对象的方法之外加入自己额外的方法
    22         
    23         method.invoke(sub, args);//反射机制
    24         
    25         System.out.println("after calling :"+ method);
    26         return null;
    27     }
    28 
    29 }

          客户端调用:    

     1 public class Client {
     2     
     3     public static void main(String[] args) {
     4         //创建被代理类的对象,根据传入的这个对象获得调用处理程序(handler)
     5         RealSubject realSubject = new RealSubject();
     6         
     7         //创建一个调用处理程序(handler),由于DynamicProxySubject类实现了InvocationHandler接口,
     8         //因此,handler可以指向DynamicProxySubject实例,在后面生成代理实例时需要传入这个handler
     9         InvocationHandler handler = new DynamicProxySubject(realSubject);
    10         
    11         Class<?> classType = handler.getClass();
    12         
    13         //一次性生成代理类的实例
    14         Subject subject = (Subject)Proxy.newProxyInstance(classType.getClassLoader(), 
    15                           realSubject.getClass().getInterfaces(), handler);
    16         
    17         //代理实例调用方法时,将其指派到它的调用处理程序(handler)的invoke方法,流程转入invoke方法的调用
    18         subject.rentHouse();
    19         
    20         //打印动态生成的代理实例的Class对象,可以看出生成的代理类的类型
    21         System.out.println(subject.getClass());
    22         
    23     }
    24 
    25 }

          以上代码是动态代理模式的实现过程的示例,与静态代理的实现过程不同,代理类需要实现InvocationHandler这个接口,此接口只有一个invoke方法,每一个代理实例都具有一个关联的调用处理程序(handler),对代理实例调用方法时,将会被与之关联的handler接管,转而调用handler的invoke方法。invoke方法的参数中,method参数表示代理实例调用的那个方法对应的Method对象,比如上面的程序里就表示rentHouse()方法的Method对象;args参数表示被调用方法的参数列表。

          Proxy类的静态方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)会动态返回一个代理类的实例,第一个参数表示代理类的类加载器;我们需要注意第二个参数,它是一个接口数组,表示代理类要实现的接口列表,也就是说动态生成的这个代理实例实现了这些接口;第三个参数表示与代理实例关联的调用处理程序(handler)。

          运行程序,我们可以得到以下结果输出:

          

          从运行结果可以看出,第一行和第三行信息是代理类中我们加入的额外的方法,第二行是代理类代理执行RealSubject类中的方法。我们来看看第四行信息,它就是我们打印的动态生成的代理类,这个类是运行期间动态生成的一个类,它既不是RealSubject类型也不是DynamicProxySubject类型,它是$Proxy0类型的。在代码中,由于我们传入的接口数组是realSubject对象所实现的接口,即Subject接口,因此生成的这个代理类也实现了Subject接口,根据多态,我们可以将生成的代理类强制转换成Subject类型。

          动态代理模式适合被代理对象类型不确定的场景,我们在代理类DynamicProxySubject中把被代理对象定义为Object类型,这样就可以代理所有类型的对象,在上面的例子中,我们传入的被代理对象是RealSubject类型。代理类是在运行时根据我们传入的被代理对象以及需要实现的接口等信息动态生成的,它的类型是不固定的(但是都是以$Proxy开头)。

          以上内容给大家介绍了代理模式的静态代理和动态代理原理,我们要根据不同的应用场景来选择用哪一种,其实最重要的是要理解这种思想,在Spring框架底层源码中很多地方也用到了代理模式,理解了代理模式对我们研究框架很有帮助。以上只是LZ个人的见解,仅供参考。

  • 相关阅读:
    python tkinter坐标转换
    python tkinter画圆
    打开pycharm提示python已停止工作
    关于python读写文件的r+方式的坑
    pycharm的一个bug,pycharm 在debug时,会运行项目下的所有文件,而不是当前文件
    python tkinter实时显示曲线
    书上关于*(p++)表达式的几种变形形式的思考题
    lua 中的 loadfile、dofile和require的调用
    css控制字体线使用:text-decoration
    devpi 快速入门:上传,测试,推送发行版
  • 原文地址:https://www.cnblogs.com/fangfuhai/p/6549777.html
Copyright © 2011-2022 走看看