关于java:一文彻底搞懂代理模式Proxy

40次阅读

共计 6010 个字符,预计需要花费 16 分钟才能阅读完成。

  • 代理模式

    • 引言
    • 代理模式的定义与特点
    • 代理模式的构造
    • 模式实现

      • 动态代理
      • [](#)
      • 动静代理
    • 总结
    • 与装璜者模式
      文章已收录我的仓库:Java 学习笔记与收费书籍分享

代理模式

引言

代理模式是十分常见的模式,在生活中的例子也十分多,例如你不好意思向你关系不太好敌人帮个忙,这时须要找一个和它关系好的应一个敌人帮忙转达,这个两头敌人就是代理对象。例如购买火车票不肯定要去火车站买,能够通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都能够通过找中介实现。

代理模式的定义与特点

代理模式的定义:因为某些起因须要给某对象 提供一个代理以管制对该对象的拜访。这时,拜访对象不适宜或者不能间接援用指标对象,代理对象作为拜访对象和指标对象之间的中介。

思考生存中一个常见的例子,客户想买房,房东有很多房,提供卖房服务,但房东不会带客户看房,于是客户通过中介买房。

你可能无奈了解这里中介是代替客户买房还是代替房东卖房,其实这是很好了解的。咱们程序编写代码是为客户服务的,中介是代替一名服务商解决业务,这种服务可能被定义为 卖房 ,也可能被定义为 帮忙客户买房,但中介唯独不可能去实现买房的性能,在代码中,咱们定义的是服务于客户的业务接口,而不是客户的需要接口,如果让客户和中介都去实现买房接口,那么这里的买房就是一种业务,服务于卖房的客户,这样房东就是客户端,买房的一方就是服务端。

但在生活中,买房的一方往往是客户端,卖房的才是服务端,因而这里中介和房东都要实现卖房的接口办法,换句话说,中介是代替房东卖房而不是代替客户买房。

客户将中介形象看成房东,间接从中介手中买房(中介 == 房东,提供卖房服务)。这里中介就是代理对象,客户是拜访对象,房东是指标对象,理论由代理齐全操控与指标对象的拜访,拜访对象客户仅与代理对象交换。

代理模式的构造

代理模式的构造比较简单,次要是通过定义一个继承形象主题的代理来蕴含实在主题,从而实现对实在主题的拜访,上面来剖析其根本构造。

代理模式的次要角色如下。

  1. 形象主题(Subject)类(业务接口类):通过接口或抽象类申明实在主题和代理对象实现的业务办法,服务端须要实现该办法。
  2. 实在主题(Real Subject)类(业务实现类):实现了形象主题中的具体业务,是代理对象所代表的实在对象,是最终要援用的对象。
  3. 代理(Proxy)类:提供了与实在主题雷同的接口,其外部含有对实在主题的援用,它能够拜访、管制或扩大实在主题的性能。

其结构图如图 1 所示。


图 1 代理模式的结构图

在代码中,个别代理会被了解为代码加强,实际上就是在原代码逻辑前后减少一些代码逻辑,而 使调用者无感知

模式实现

依据代理的创立期间,代理模式分为动态代理和动静代理。

  • 动态:由程序员创立代理类或特定工具主动生成源代码再对其编译,在程序运行前代理类的 .class 文件就曾经存在了。
  • 动静:在程序运行时,使用反射机制动态创建而成。

动态代理

动态代理服务于单个接口,咱们来思考理论工程中的一个例子,当初曾经有业务代码实现一个增删性能,原有的业务代码因为仍有大量程序无奈扭转,当初新增需要,即当前每执行一个办法输入一个日志。

咱们不扭转原有代码而增加一个代理来实现:

// 业务接口
interface DateService {void add();
    void del();}

class DateServiceImplA implements DateService {
    @Override
    public void add() {System.out.println("胜利增加!");
    }

    @Override
    public void del() {System.out.println("胜利删除!");
    }
}

class DateServiceProxy implements DateService {DateServiceImplA server = new DateServiceImplA();

    @Override
    public void add() {server.add();
        System.out.println("程序执行 add 办法,记录日志.");
    }
    @Override
    public void del() {server.del();
        System.out.println("程序执行 del 办法,记录日志.");
    }
}

// 客户端
public class Test {public static void main(String[] args) {DateService service = new DateServiceProxy();
        service.add();
        service.del();}
}

当初,我么胜利的在不改变程序原有代码的状况下,扩大了一些性能!

咱们来思考一下这种状况,当原有的业务解决因为某种原因无奈扭转,而目前又须要扩大一些性能,此时能够通过代理模式实现:

如上图所示,咱们原有的业务非常宏大,牵一发而动全身,难以批改,而当初须要扩大一些性能,这里就须要代理模式实现,在纵向代码之间,横向扩大一些性能,这也是所谓的面向切面编程。

如果你设计思维比拟良好的话,你很快就能发现下面代码的有余:一个代理只能服务于一个特定的业务实现类,假如咱们又另外一个类也实现了业务接口,即class DateServiceImplB implements DateService,发现想要扩大该类必须要为其也编写一个代理,扩展性极低。想要解决这个问题也是很简略的,咱们面向接口编程而不是面向实现,咱们给代理类持有接口而不是持有具体的类:

class DateServiceProxy implements DateService {
    DateService server;

    public DateServiceProxy(DateService server) {this.server = server;}
}

这样一个代理就能够同时代理多个实现了同一个业务接口的业务,但这种形式必须要求客户端传入一个具体的实现类,这样客户就必须要取得具体指标对象实例,指标对象就间接裸露在拜访对象背后了 ,对于某些状况这是不可承受的,例如你想取得某资源,但须要肯定的权限,这时由代理管制你对指标资源对象的拜访,不能由你间接区去拜访,这是代理就必须将指标资源对象牢牢的管制在本人手中, 前面会讲到这其实就是爱护代理。但在这里,这种办法是能够承受的,并且带给程序较高的灵活性。

动静代理

咱们为什么须要动静代理?要了解这一点,咱们必须要晓得动态代理有什么不好,要实现动态代理,咱们必须要提前将代理类硬编码在程序中,这是固定死的,下面也提到过,有一些代理一个代理就必须要负责一个类,这种状况下代理类的数量可能是十分多的,但咱们真的每个代理都会用上吗?例如,在一般的我的项目中,可能 99% 的工夫都仅仅只是简略的查问,而不会设计到增删性能,此时是不须要咱们的增删代理类的,但在动态代理中,咱们依然必须硬编码代理类,这就造成了不必要的资源节约并且减少了代码量。

动静代理能够帮忙咱们仅仅在须要的时候再创立代理类,缩小资源节约,此外因为动静代理是一个 模板的模式,也能够缩小程序的代码量,例如在动态代码示例中,咱们在每个办法中退出System.out.println("程序执行 *** 办法,记录日志.");,当业务办法十分多时,咱们也得为每个业务办法加上记录日志的语句,而动静代理中将办法对立治理,无论几个业务办法都只须要一条记录语句即可实现,具体请看代码。

动静代理采纳 反射 的机制,在运行时创立一个接口类的实例。在 JDK 的实现中,咱们须要借助 Proxy 类和 InvocationHandler 接口类。

在运行期动态创建一个 interface 实例的办法如下:

  1. 定义一个类去实现 InvocationHandler 接口,这个接口下有一个 invoke(Object proxy, Method method, Object[] args) 办法,它负责调用对应接口的接口办法;

    调用代理类的办法时,处理程序会利用反射,将代理类、代理类的办法、要调用代理类的参数传入这个函数,并运行这个函数,这个函数是理论运行的,咱们在这里编写代理的外围代码。

  2. 通过 Proxy.newProxyInstance() 创立某个 interface 实例,它须要 3 个参数:

    1. 应用的ClassLoader,通常就是接口类的ClassLoader
    2. 须要实现的接口数组,至多须要传入一个接口进去;
    3. 一个处理程序的接口。

    这个办法返回一个代理类 $Proxy0,它有三个参数,第一个通常是类自身的 ClassLoader,第二个是该类要实现的接口,例如这里咱们要实现增删接口,第三个是一个解决程序接口,即调用这个类的办法时,这个类的办法会被委托给该处理程序,该处理程序做一些解决,这里对应了下面这个办法,通常设置为 this。

  3. 将返回的 Object 强制转型为接口。

来看一下具体实现:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 业务接口
interface DateService {void add();
    void del();}

class DateServiceImplA implements DateService {
    @Override
    public void add() {System.out.println("胜利增加!");
    }

    @Override
    public void del() {System.out.println("胜利删除!");
    }
}

class ProxyInvocationHandler implements InvocationHandler {
    private DateService service;

    public ProxyInvocationHandler(DateService service) {this.service = service;}

    public Object getDateServiceProxy() {return Proxy.newProxyInstance(this.getClass().getClassLoader(), service.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {var result = method.invoke(service, args); // 让 service 调用办法,办法返回值
        System.out.println(proxy.getClass().getName() + "代理类执行" + method.getName() + "办法,返回" + result +  ",记录日志!");
        return result;
    }
}

// 客户端
public class Test {public static void main(String[] args) {DateService serviceA = new DateServiceImplA();
        DateService serviceProxy = (DateService) new ProxyInvocationHandler(serviceA).getDateServiceProxy();
        serviceProxy.add();
        serviceProxy.del();}
}
/*
胜利增加!$Proxy0 代理类执行 add 办法,返回 null,记录日志!胜利删除!$Proxy0 代理类执行 del 办法,返回 null,记录日志!*/

咱们代理类是通过 Proxy.newProxyInstance(this.getClass().getClassLoader(),service.getClass().getInterfaces(), this); 办法失去的,这个办法中,第二个参数咱们传入了类 service 的接口局部,即 DateService,在底层通过该接口的字节码帮咱们创立一个新类 $Proxy0,该类具备接口的全副办法。第三个参数是一个解决程序接口,此处传入 this 即表明将办法交给 ProxyInvocationHandler 的接口即 InvocationHandler 的 invoke 办法执行。

$Proxy 并不具备真正解决的能力,当咱们调用 $$Proxy0.add()时,会陷入 invoke 处理程序,这是咱们编写外围代码的中央,在这里 var result = method.invoke(service, args); 调用指标对象的办法,咱们能够编写代理的外围代码。

总结

代理模式通常有如下几种用处:

  • 近程代理,这种形式通常是为了暗藏指标对象存在于不同地址空间的事实,不便客户端拜访。例如,用户申请某些网盘空间时,会在用户的文件系统中建设一个虚构的硬盘,用户拜访虚构硬盘时理论拜访的是网盘空间。
  • 虚构代理,这种形式通常用于要创立的指标对象开销很大时。例如,下载一幅很大的图像须要很长时间,因某种计算比较复杂而短时间无奈实现,这时能够先用小比例的虚构代理替换实在的对象,打消用户对服务器慢的感觉。
  • 爱护代理,当对指标对象拜访须要某种权限时,爱护代理提供对指标对象的受控爱护,例如,它能够拒绝服务权限不够的客户。
  • 智能指引,次要用于调用指标对象时,代理附加一些额定的解决性能。例如,减少计算实在对象的援用次数的性能,这样当该对象没有被援用时,就能够主动开释它(C++ 智能指针);例如下面的房产中介代理就是一种智能指引代理,代理附加了一些额定的性能,例如带看房等。

代理模式的次要长处有:

  • 代理模式在客户端与指标对象之间起到一个中介作用和爱护指标对象的作用;
  • 代理对象能够扩大指标对象的性能;
  • 代理模式能将客户端与指标对象拆散,在肯定水平上升高了零碎的耦合度,减少了程序的可扩展性;

其次要毛病是:

  • 动态代理模式会造成零碎设计中类的数量减少,但动静代理能够解决这个问题;
  • 在客户端和指标对象之间减少一个代理对象,会造成申请处理速度变慢;
  • 减少了零碎的复杂度;

与装璜者模式

咱们实现的代理模式和装璜者模式非常相像,但他们的目标不同。在下面咱们提到过,某些代理会严格将拜访对象和受控对象拆散开来,一个代理仅仅只负责一个类,这与装璜器模式是不同的,对于装璜器模式来说,指标对象就是拜访对象所持有的。此外虚构代理的实现与装璜者模式实现是不同的,虚构代理一开始并不持有近程服务器的资源对象,而是对域名和文件名进行解析才失去该对象,这与咱们下面的代码都是不同的,在咱们的代码中咱们要么传入一个实例,要么让代理持有一个实例,但在虚构代理中,我么传入一个虚构的文件资源,虚构代理对近程服务器进行解析才会取得实在的对象实例,这一点也是不同的。

正文完
 0