zoukankan      html  css  js  c++  java
  • Java设计模式-代理模式

    设计模式的分类

    25种

    总体来说设计模式分为三大类:

    创建型模式,共五种:工厂方法模式抽象工厂模式单例模式建造者模式原型模式

    结构型模式,共七种:适配器模式装饰器模式代理模式外观模式桥接模式组合模式享元模式

    行为型模式,共十一种:策略模式模板方法模式观察者模式迭代子模式责任链模式命令模式备忘录模式状态模式访问者模式中介者模式解释器模式

    其实还有两类:并发型模式线程池模式

    前言

    学习了java的反射,其中反射有一条知识点就是可以用于生成代理模式,以此,将Java代理模式讲解一遍,还没有看过反射的同学可以去我的博客,先阅读反射的相关知识,以助于你更好的理解Java的代理模式。

    简介

    什么是代理:

    Proxy(代理)

    代理分为静态代理动态代理,静态代理是在编译时就将接口、实现类、代理类一股脑儿全部手动完成,但如果我们需要很多的代理,每一个都这么手动的去创建实属浪费时间,而且会有大量的重复代码,此时我们就可以采用动态代理,动态代理可以在程序运行期间根据需要动态的创建代理类及其实例,来完成具体的功能,主要用的是JAVA的反射机制

    其实方法直接调用就可以完成功能,为什么还要加个代理呢?
    原因是采用代理模式可以有效的将具体的实现与调用方进行解耦,通过面向接口进行编码完全将具体的实现隐藏在内部。

    Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题

    ps:面试有可能会问到哦

    代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

    更通俗的说,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

    按照代理的创建时期,代理类可以分为两种:

    • 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。

    • 动态:在程序运行时运用反射机制动态创建而成。

    静态代理

    实代理的一般模式就是静态代理的实现模式:首先创建一个接口(JDK代理都是面向接口的),然后创建具体实现类来实现这个接口,在创建一个代理类同样实现这个接口,不同指出在于,具体实现类的方法中需要将接口中定义的方法的业务逻辑功能实现,而代理类中的方法只要调用具体类中的对应方法即可,这样我们在需要使用接口中的某个方法的功能时直接调用代理类的方法即可,将具体的实现类隐藏在底层。

    第一步:定义总接口Iuser.java

    1
    2
    3
    public interface Iuser {
    void eat(String s);
    }

    第二步:创建具体实现类UserImpl.java

    被代理人

    1
    2
    3
    4
    5
    6
    public class UserImpl implements Iuser {
      @Override
      public void eat(String s) {
        System.out.println("我要吃"+s);
      }
    }

    第三步:创建代理类UserProxy.java

    代理人

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class UserProxy implements Iuser {
      private Iuser user = new UserImpl();
      @Override
      public void eat(String s) {
        System.out.println("静态代理前置内容");
        user.eat(s);
        System.out.println("静态代理后置内容");
      }
    }

    第四步:创建测试类ProxyTest.java

    1
    2
    3
    4
    5
    6
    public class ProxyTest {
      public static void main(String[] args) {
        UserProxy proxy = new UserProxy();
        proxy.eat("苹果");
      }
    }

    运行结果:

    静态代理前置内容
    我要吃苹果
    静态代理后置内容
    

    综上的代码和输出结果可以看出,静态代理的实现方法还是很简单的。都需要实现总接口,代理人里面持有被代理人的对象。代理人可以根据情况的不同,添加一些操作。

    静态代理类优缺点

    • 优点:

    代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合)。

    • 缺点:

    1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

    2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了

    举例说明:代理可以对实现类进行统一的管理,如在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所说的开闭原则。但是如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能(如上的代理方法中删除,修改,以及查询都需要添加上打印日志的功能)
    即静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。

    动态代理

    动态代理的思维模式与之前的一般模式是一样的,也是面向接口进行编码,创建代理类将具体类隐藏解耦,不同之处在于代理类的创建时机不同,动态代理需要在运行时因需实时创建。

    第一步:定义总接口Iuser.java

    1
    2
    3
    public interface Iuser {
      void eat(String s);
    }

    第二步:创建具体实现类UserImpl.java

    被代理人

    1
    2
    3
    4
    5
    6
    public class UserImpl implements Iuser {
      @Override
      public void eat(String s) {
        System.out.println("我要吃"+s);
      }
    }

    第三步:创建实现InvocationHandler接口的代理类

    代理人

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法。该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类  
    public class DynamicProxy implements InvocationHandler {
      private Object object;//用于接收具体实现类的实例对象
      //使用带参数的构造器来传递具体实现类的对象
      public DynamicProxy(Object obj){
        this.object = obj;
      }
      @Override
      public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
        System.out.println("前置内容");
        method.invoke(object, args);
        System.out.println("后置内容");
        return null;
      }
    }

    第四步:创建测试类ProxyTest.java

    1
    2
    3
    4
    5
    6
    7
    8
    public class ProxyTest {
      public static void main(String[] args) {
        Iuser user = new UserImpl();
        InvocationHandler h = new DynamicProxy(user);
        Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
        proxy.eat("苹果");
      }
    }

    运行结果为:

    动态代理前置内容
    我要吃苹果
    动态代理后置内容
    

    动态代理的实现过程

    1. 首先我要说的就是接口,为什么JDK的动态代理是基本接口实现的呢?
      因为通过使用接口指向实现类的实例的多态实现方式,可以有效的将具体的实现与调用之间解耦,便于后期修改与维护。
    再具体的说就是我们在代理类中创建一个私有成员变量(private修饰),使用接口来指向实现类的对象(纯种的多态体现,向上转型的体现),然后在该代理类中的方法中使用这个创建的实例来调用实现类中的相应方法来完成业务逻辑功能。
    这么说起来,我之前说的“将具体实现类完全隐藏”就不怎么正确了,可以改成,将具体实现类的细节向调用方完全隐藏(调用方调用的是代理类中的方法,而不是实现类中的方法)。
      这就是面向接口编程,利用java的多态特性,实现程序代码的解耦。

    2. 创建代理类的过程
      如果你了解静态代理,那么你会发现动态代理的实现其实与静态代理类似,都需要创建代理类,但是不同之处也很明显,创建方式不同!
      不同之处体现在静态代理我们知根知底,我们知道要对哪个接口、哪个实现类来创建代理类,所以我们在编译前就直接实现与实现类相同的接口,直接在实现的方法中调用实现类中的相应(同名)方法即可;而动态代理不同,我们不知道它什么时候创建,也不知道要创建针对哪个接口、实现类的代理类(因为它是在运行时因需实时创建的)。
      虽然二者创建时机不同,创建方式也不相同,但是原理是相同的,不同之处仅仅是:静态代理可以直接编码创建,而动态代理是利用反射机制来抽象出代理类的创建过程。

    分析

    1. 静态代理需要实现与实现类相同的接口,而动态代理需要实现的是固定的Java提供的内置接口(一种专门提供来创建动态代理的接口)InvocationHandler接口,因为java在接口中提供了一个可以被自动调用的方法invoke,这个之后再说。

    2. 先看代码

    1
    2
    3
    private Object object;
            public UserProxy(Object obj){
    this.object = obj;}

      这几行代码与静态代理之中在代理类中定义的接口指向具体实现类的实例的代码异曲同工,通过这个构造器可以创建代理类的实例,创建的同时还能将具体实现类的实例与之绑定(object指的就是实现类的实例,这个实例需要在测试类中创建并作为参数来创建代理类的实例),实现了静态代理类中private Iuser user = new UserImpl();一行代码的作用相近,这里为什么不是相同,而是相近呢,主要就是因为静态代理的那句代码中包含的实现类的实例的创建,而动态代理中实现类的创建需要在测试类中完成,所以此处是相近。

    3. invoke(Object proxy, Method method, Object[] args)方法,该方法是InvocationHandler接口中定义的唯一方法,该方法在调用指定的具体方法时会自动调用。其参数为:代理实例、调用的方法、方法的参数列表
      在这个方法中我们定义了几乎和静态代理相同的内容,仅仅是在方法的调用上不同,不同的原因与之前分析的一样(创建时机的不同,创建的方式的不同,即反射),Method类是反射机制中一个重要的类,用于封装方法,该类中有一个方法那就是invoke(Object object,Object…args)方法,其参数分别表示:所调用方法所属的类的对象和方法的参数列表,这里的参数列表正是从测试类中传递到代理类中的invoke方法三个参数中最后一个参数(调用方法的参数列表)中,在传递到method的invoke方法中的第二个参数中的(此处有点啰嗦)。

    4. 测试类中的异同
      静态代理中我们测试类中直接创建代理类的对象,使用代理类的对象来调用其方法即可,若是别的接口(这里指的是别的调用方)要调用Iuser的方法,也可以使用此法
    动态代理中要复杂的多,首先我们要将之前提到的实现类的实例创建(补充完整),然后利用这个实例作为参数,调用代理来的带参构造器来创建“代理类实例对象”,这里加引号的原因是因为它并不是真正的代理类的实例对象,而是创建真正代理类实例的一个参数,这个实现了InvocationHandler接口的类严格意义上来说并不是代理类,我们可以将其看作是创建代理类的必备中间环节,这是一个调用处理器,也就是处理方法调用的一个类,不是真正意义上的代理类,可以这么说:创建一个方法调用处理器实例。
      下面才是真正的代理类实例的创建,之前创建的”代理类实例对象“仅仅是一个参数
        Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
      这里使用了动态代理所依赖的第二个重要类Proxy,此处使用了其静态方法来创建一个代理实例,其参数分别是:类加载器(可为父类的类加载器)、接口数组、方法调用处理器实例
      这里同样使用了多态,使用接口指向代理类的实例,最后会用该实例来进行具体方法的调用即可。

    动态代理优点

    动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强

    总结

    其实所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用。

    代理对象就是把被代理对象包装一层,在其内部做一些额外的工作,比如用户需要上facebook,而普通网络无法直接访问,网络代理帮助用户先翻墙,然后再访问facebook。这就是代理的作用了。

    纵观静态代理与动态代理,它们都能实现相同的功能,而我们看从静态代理到动态代理的这个过程,我们会发现其实动态代理只是对类做了进一步抽象和封装,使其复用性和易用性得到进一步提升而这不仅仅符合了面向对象的设计理念,其中还有AOP的身影,这也提供给我们对类抽象的一种参考。关于动态代理与AOP的关系,个人觉得AOP是一种思想,而动态代理是一种AOP思想的实现!

  • 相关阅读:
    CentOS 7 安装Hadoop前的SSH免密码登录配置
    CentOS 7.1下SSH远程登录服务器详解-转
    Linux系统下如何配置SSH_Centos7 ssh连接配置 CentOS7下安全配置
    如何在CentOS 7上修改主机名
    【转】CentOS 6.3(x86_64)下安装Oracle 10g R2
    【转】CentOS 6.3(x86_32)下安装Oracle 10g R2
    【转】Linux Oracle服务启动&停止脚本与开机自启动
    Cacti在selinux开启的情况下使用
    Nagios在selinux开启的情况下使用
    Nagios状态长时间处于Pending的解决方法
  • 原文地址:https://www.cnblogs.com/ziq711/p/8270265.html
Copyright © 2011-2022 走看看