设计模式(7):代理模式

一、什么是代理模式

「代理模式」是一种结构型设计模式, 能够提供对象的替代品或其占位符(「即是给某一个对象提供一个代理,并由代理对象控制对原对象的引用」)。代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。

例如电脑桌面的快捷方式就是一个代理对象,快捷方式是它所引用的程序的一个代理。

设计模式(7):代理模式

「主要作用」:为其他对象提供一种代理以控制对这个对象的访问。

「何时使用」:想在访问一个类时做一些控制。

「如何解决」:增加中间层。

「关键代码」:实现与被代理类组合。

「主要解决」:在直接访问对象时带来的问题,以及对一些对象的增强。

设计模式(7):代理模式

二、代理模式的应用场景

2.1 代理模式的种类

按职责来划分,通常有以下使用场景:

  • 「远程代理(Remote Proxy)」

    为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)。

  • 「延迟初始化/虚拟代理(Virtual Proxy)」

    「如果你有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。」

    无需在程序启动时就创建该对象, 可将对象的初始化延迟到真正有需要的时候。

  • 「Copy-on-Write 代理」

  • 「访问控制/保护代理(Protect Proxy)」

    「控制对一个对象的访问,可以根据客户端凭据给不同的用户提供不同级别的使用权限。」

    如果只希望特定客户端使用服务对象,这里的对象可以是操作系统中非常重要的部分,而客户端则是各种已启动的程序 (包括恶意程序), 此时可使用代理模式

  • 「缓冲代理(Cache Proxy)」

    为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。

  • 「防火墙(Firewall)代理」

  • 「同步化(Synchronization)代理」

  • 「智能引用代理(Smart Reference Proxy)」

    「可在没有客户端使用某个重量级对象时立即销毁该对象」。还可以记录客户端是否修改了服务对象,其他客户端还可以复用未修改的对象。

    代理会将所有获取了指向服务对象或其结果的客户端记录在案。代理会时不时地遍历各个客户端, 检查它们是否仍在运行。如果相应的客户端列表为空, 代理就会销毁该服务对象, 释放底层系统资源。

2.2 使用场景

  • Windows 里面的快捷方式。
  • 买火车票不一定在火车站买,也可以去代售点。
  • 一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金并提供对签发人账号上资金的制。
  • Spring aop。

三、代理模式的优缺点

「优点:」

  • 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
  • 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。

「缺点:」

  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。
  • 实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。

四、代理模式的结构

  • 「Client (客户端)」

    客户端访问代理者与访问被代理者具有类似的效果,其无法区分访问的是代理者还是被代理者。

  • 「Subject(抽象主题角色)」

    代理者与被代理者共同实现的接口,在任何使用真实主题的地方都可以使用代理主题。

  • 「Proxy(代理主题角色)」

    代理者,会全权代理RealSubject所具有的功能,在实现其功能的基础上做一些额外的工作;

    • 「包含了对真实主题的引用」,从而可以在任何时候操作真实主题对象;
    • 在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;
    • 代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。
  • 「RealSubject(真实主题角色)」

    被代理者,代理角色所代表的真实对象,其为具有某种特定行为的实现者。

设计模式(7):代理模式

四、代理模式实现方式

  • 定义一个抽象主题接口,确定代理类与被代理类的行为方法

  • 创建真实主题角色并实现抽象主题接口,实现某种特定行为

  • 创建代理类并实现抽象主题接口, 其中必须包含一个存储指向服务的引用的成员变量。

    通常情况下, 代理负责创建服务并对其整个生命周期进行管理。在一些特殊情况下, 客户端会通过构造函数将服务传递给代理。

  • 根据需求实现代理方法。在大部分情况下, 代理在完成一些任务后应将工作委派给服务对象。

  • 可以考虑新建一个构建方法来判断客户端可获取的是代理还是实际服务。

    可以在代理类中创建一个简单的静态方法, 也可以创建一个完整的工厂方法。

  • 可以考虑为服务对象实现延迟初始化。

    如果你有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。

五、代理模式的两种实现

5.1 静态代理

  • 抽象主题接口

    /**
    * @description: 抽象主题接口
    */

    public interface IUserDao {
    /**
    * 行为方法
    */

    void save();

    }
  • 真实主题角色

    /**
    * @description: 真实主题角色并实现抽象主题接口 被代理类
    */

    public class UserDao implements IUserDao{
    @Override
    public void save() {
    System.out.println("真实主题角色 ========>>>>>>>被代理类");
    }
    }
  • 代理主题角色

    /**
    * @description: 代理类并实现抽象主题接口
    */

    public class UserDaoProxy implements IUserDao {

    // 存储指向服务的引用的成员变量 接收保存目标对象
    private IUserDao proxy;

    public UserDaoProxy(IUserDao proxy){
    this.proxy=proxy;
    }

    @Override
    public void save() {
    System.out.println("代理类主题角色 ========>>>>>>>代理类");
    proxy.save();
    System.out.println("代理类主题角色 ========>>>>>>>代理类");
    }
    }
  • 客户端

    public static void main(String[] args) throws Throwable {
    //目标对象
    UserDao userDao = new UserDao();
    //代理对象,把目标对象传给代理对象,建立代理关系
    UserDaoProxy proxy = new UserDaoProxy(userDao);
    //执行代理的方法
    proxy.save();
    }

「静态代理总结:」

  • 「优点」:可以做到在不修改目标对象的功能前提下,对目标功能扩展.

  • 「缺点」:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,同时,一旦接口增加方法,目标对象与代理对象都要维护.

5.2 动态代理

5.2.1 JDK动态代理

「JDK的动态代理只能针对实现了接口的类生成代理」。原因是:

  • 从原理上讲因为JVM动态生成的代理类时「继承了Proxy类,实现了代理的接口,最终形式如下(HelloInterface为被代理类实现的接口)」

    public final class $Proxy0 extends Proxy implements HelloInterface{
    }
  • 从使用上讲,「创建代理类时必须传入被代理类实现的接口」。 而Java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。

5.2.1.1 JDK动态代理原理

「JDK动态代理是利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理」

在JDK动态代理中,有一个类和接口是实现动态代理所必须用到的, InvocationHandler(Interface)、 Proxy(Class)

  • 「InvocationHandler(Interface)」

    「每一个动态代理类都必须要实现接口InvocationHandler,并且每个代理类的实例都关联了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。」

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

    这个方法一共接受三个参数,那么这三个参数分别代表如下:

    • proxy:  JDK动态生成的最终代理对象
    • method: 所要调用真实对象的某个方法的Method对象
    • args: 调用真实对象某个方法时接受的参数
  • 「Proxy(Class)」

    「用来动态创建一个代理对象的类,它提供了许多的方法,但是用的最多的是newProxyInstance :」

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler handler)  throws IllegalArgumentException

    这个方法的作用就是得到一个动态的代理对象,其接收三个参数:

    DynamicProxy(动态代理类)是这样一种class:它是在运行时生成的class,在生成它时必须提供一组interface给它,然后该class就宣称它实现了这些 interface。这个DynamicProxy其实就是一个Proxy,它不会做实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。

    • loader:  ClassLoader对象

      定义了由哪个ClassLoader来对生成的代理对象进行加载。

    • interfaces:  Interface对象的数组

      Interface对象的数组,表示的是将要给需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样就能调用这组接口中的方法了。

    • Handler   InvocationHandler对象

      表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。

5.2.1.2 JDK动态代理实例
  • 抽象主题接口

    /**
    * @description: 抽象主题接口
    */

    public interface IUserDao {
    /**
    * @description: 行为方法
    */

    void save();
    }
  • 真实主题角色

    /**
    * @description: 真实主题角色并实现抽象主题接口 被代理类
    */

    public class UserDao implements IUserDao{
    @Override
    public void save() {
    System.out.println("真实主题角色 ========>>>>>>>被代理类");
    }
    }

  • 代理主题角色

    /**
    * 每次生成动态代理类对象时都需要指定一个实现了InvocationHandler接口的调用处理器对象
    * @date 2021/10/2111:17
    */

    public class JdkProxyHandler implements InvocationHandler {

    // 存储指向服务的引用的成员变量 接收保存目标对象 真正执行业务逻辑的类
    private Object jdkProxy;
    // 通过构造方法传入这个被代理对象
    public JdkProxyHandler(Object jdkProxy){
    this.jdkProxy=jdkProxy;
    }

    /**
    *当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
    */

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("可以在调用实际方法前做一些事情");
    Object result = method.invoke(jdkProxy, args);// 需要指定被代理对象和传入参数
    System.out.println("可以在调用实际方法后做一些事情");
    return result;// 返回method方法执行后的返回值
    }
    }

  • 客户端

       public static void main(String[] args) {

    //第一步:创建被代理对象
    UserDao userDao = new UserDao();
    //第二步:创建handler,传入真实对象
    JdkProxyHandler handler = new JdkProxyHandler(userDao);
    //第三步:创建代理对象,传入类加载器、接口、handler
    IUserDao iUserDao = (IUserDao) Proxy.newProxyInstance(
    Domemain.class.getClassLoader(),//第一个参数,获取ClassLoader
    userDao.getClass().getInterfaces(), //第二个参数,获取被代理类的接口
    //第三个参数,InvocationHandler对象,表示当这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
    handler)
    ;
    //第四步:调用方法
    iUserDao.save();
    }

5.2.1.3 总结

通过上面的例子三个参数可得出,「JDK的动态代理依靠接口实现,入参必须有被代理类的接口」,也就是carImpl.getClass().getInterfaces(),如果有些类并没有实现接口,则不能使用JDK代理,需要使用cglib动态代理。

5.2.2 Cglib动态代理

5.2.2.1 Cglib动态代理原理

「CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的方式拦截所有父类方法的调用,顺势织入横切逻辑。」(利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理)

在 Cglib动态代理中,同样有一个类和接口是实现动态代理所必须用到的, MethodInterceptor(Interface)、 Enhancer(Class)

  • 「net.sf.cglib.proxy.Enhancer」

    • 主要增强类,通过字节码技术动态创建委托类的子类实例。
    • Enhancer是CGLIB中最常用的一个类,和Java动态代理中引入的Proxy类差不多。和Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。
    • Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。
    • Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对fianl类进行代理操作。这也是Hibernate为什么不能持久化final class的原因。
  • 「net.sf.cglib.proxy.MethodInterceptor」

    常用的方法拦截器接口,需要实现intercept方法,实现具体拦截处理;

     public java.lang.Object intercept(java.lang.Object obj,
    java.lang.reflect.Method method,
    java.lang.Object[] args,
    MethodProxy methodProxy)

    throws java.lang.Throwable
    {}

    • obj:动态生成的代理对象

    • method :实际调用的方法

    • args:调用方法入参

    • methodProxy:java Method类的代理类,可以实现委托类对象的方法的调用;

      常用方法:methodProxy.invokeSuper(obj, args);在拦截方法内可以调用多次

5.2.2.2 Cglib动态代理实例
  • 真实主题角色

    /**
    * @description: 真实主题角色并实现抽象主题接口 被代理类
    * @author
    * @date 2021/10/21 10:36
    */

    public class UserDaoNoInterface{

    public void save() {
    System.out.println("真实主题角色 ========>>>>>>>被代理类 没有实现接口");
    }
    }

  • 代理主题角色

    /**
    * 每次生成动态代理类对象时都需要指定一个实现了InvocationHandler接口的调用处理器对象
    * @date 2021/10/2111:17
    */

    public class CglibProxyHandler implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz){
    //设置需要创建子类的类
    enhancer.setSuperclass(clazz);
    enhancer.setCallback(this);
    //通过字节码技术动态创建子类实例
    return enhancer.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    System.out.println("可以在调用实际方法前做一些事情");
    //通过代理类调用父类中的方法
    Object result = methodProxy.invokeSuper(obj, objects);
    System.out.println("可以在调用实际方法后做一些事情");
    return result;
    }
    }

  • 客户端

    public static void main(String[] args) {
    CglibProxyHandler proxy = new CglibProxyHandler();
    //通过生成子类的方式创建代理类
    UserDaoNoInterface iUserDao = (UserDaoNoInterface)proxy.getProxy(UserDaoNoInterface.class);
    iUserDao.save();
    }

六、与其他模式的关系

6.1 代理模式和装饰模式的异同

代理模式和装饰模式的实现方式类似,有着相似的结构,主要不同点:

  • 代理模式自行管理其服务对象的生命周期,装饰模式的生成则总是由客户端进行控制。
  • 代理模式关注与被代理对象行为的控制,装饰模式关注于在一个对象上动态的添加方法。
  • 代理模式可以对客户端隐藏被代理对象的具体实现,装饰模式是将原始对象转为一个参数传递给装饰者的构造器中
  • 代理模式能为对象「提供相同的接口」,装饰模式则能为对象「提供加强的接口」

代理模式强调的是「限制」,装饰模式强调的是「增强」

6.2 代理模式和委托模式的异同

  • 「代理模式」:是把一些事情交给某人帮忙去完成。
  • 「委托模式」:是当某件事情发生的时候,顺便干某件事情。委托就相当于一个触发器。

6.3 代理模式和适配器模式的异同

  • 「代理模式」:不能改变所代理类的接口,只是增加限制
  • 「适配器模式」:主要改变所考虑对象的接口,能为被封装对象「提供不同的接口」

七、总结

  • 目标对象有实现接口,用JDK代理
  • 如果目标对象没有实现接口,用Cglib代理


原文始发于微信公众号(星河之码):设计模式(7):代理模式

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/27181.html

(1)

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!