代理模式

来源:互联网 发布:e.abchina.com js 编辑:程序博客网 时间:2024/05/29 03:02

    • 模式描述
    • 模式类图
    • 模式实现
      • Subject
      • RealSubject
      • Proxy
      • Client
      • 运行结果
    • 模式扩展
      • 1 通过继承实现代理
      • 2 动态代理
    • 适用场景
        • 1 远程代理
        • 2 虚拟代理
        • 3 保护代理
        • 4 缓存代理
        • 5 同步代理
        • 6 智能引用代理
        • 7 日志代理
        • 8 分布式代理
    • 权衡点
        • 优点
        • 缺点
    • 应用案例
    • 相关原则
    • 相关模式
    • 问题思考
    • 参考资料

定义: Provide a surrogate(代理) or placeholder(占位符) for another object to control access(控制访问) or append ability(赋能) to it.
类型: 结构型模式
场景: 访问控制,赋能
思路: 为调用者和被调用者之间留有余地,以应对变化。

1. 模式描述


在调用者和被调用者之间添加一个代理层,由代理层控制目标对象的引用,作为被调用者的替身,增加了访问的封装性、灵活性、扩展性。

  • 封装性:代理封装了被调用者的访问细节,不需要上层调用者了解细节和介入实现;
  • 灵活性:代理层对被调用者的时机方式可以灵活控制;
  • 扩展性:代理层可以给被调用者赋予新的能力,或者强化已有能力。

代理模式给调用者和被调用者之间增加了一个缓冲,从而为以后的变化留有余地,从一定程度上降低了耦合。


代理层示意

2. 模式类图



代理模式类图--基本类图

代理模式主要包含如下4个角色:

  • Subject(被调用者的抽象):定义被调用者想要/能够提供的服务。request()方法是要提供的服务;

    注:此处对于抽象类的理解既可以是一个抽象类(abstract class),也可以是一个或者多个接口(interface),根据被访问对象实际需要暴露和提供的能力而定,不需要局限于类图。

  • RealSubject(被调用者的具体实现):服务的具体实现;

  • Client(调用者) : 服务的调用者,不局限于单个对象,可以是任何上层应用或者服务;
  • Proxy(访问代理):作为”代理”存在于调用者和被调用者之间,代替调用者访问目标服务;代理可以自行决定何时以及何种方式调用request()方法,同时提供额外的before()、after()等额外方法用于扩展目标服务的能力;

    注:设计模式提供的是一种设计思路,所以代理模式的实现并不局限于类图的实现,其中的角色依据看待问题的视角可大可小,即可以是一个具体的类或者对象,也可以是一个组件、一个服务、一种资源…


代理模式中的Proxy和RealSubject就好比经纪人和明星的关系。经纪人负责明星的公共事务,比如:签约、谈片酬、财务管理,危机公关等,这些事情都可以划在Subject范畴,即经纪人的本职工作。同时经纪人本身又可以做一些自己的事情,比如:transfer the actor’s property and visit his wife sometime.

3. 模式实现


Subject

/** * 被调用者的抽象 */public interface Subject {    /**     * 服务抽象     */    void request();}

RealSubject

/** * 被调用者的具体实现 */public class RealSubject implements Subject {    @Override    public void request() {        // 服务的具体实现...        System.out.println("real service is called.");    }}

Proxy

/** * 代理类 */public class Proxy implements Subject {    // 存放被代理的对象    private Subject subject;    // 注入方式可扩展,亦可以是setter注入、通过元数据生成...    public Proxy(Subject subject) {        // 被代理的对象由上层决定,传入什么对象,就代理什么对象;        this.subject = subject;    }    @Override    public void request() {        // 代理操作的实现,可以根据实际扩展,充满想象...        // 调用方式:同步/异步...        // 调用时机:立即/延迟...        // 调用目的:扩展功能、权限控制、过滤...        before();        subject.request();        after();    }    /**     * 对目标对象服务的具体定制,可以是一群方法、类、服务...     */    public void before() {        System.out.println("called before request().");    }    public void after() {        System.out.println("called after request().");    }}

Client

/** * 调用者 */public class Client {    public static void main(String[] args) {        // 目标对象和代理的创建时间和地点可以不同,此处仅示意        RealSubject subject = new RealSubject();        Proxy proxy = new Proxy(subject);        // 执行代理方法        proxy.request();    }}

运行结果

called before request().
real service is called.
called after request().

4. 模式扩展

4.1 通过继承实现代理

代理模式还有一种简化模式,即代理类直接继承被调用者,而无须继承抽象接口。根据继承的特性,子类可以通过super关键字调用父类的公有方法,从而实现代理操作。类图如下:


这里写图片描述

通过继承实现代理的虽然方式简单,但是很显然违背了合成复用原则。然而我们不是为了模式而模式,原则只是为了更好的指导软件开发,而并非强制的规则,有时为了满足业务需求,违背原则也无妨。简化模式的具体实现如下,RealSubject无变化,此处仅列出代理类:

/** * 代理类 */public class Proxy extends RealSubject {    @Override    public void request() {        // 代理操作的实现,可以根据实际扩展,充满想象...        // 调用方式:同步/异步...        // 调用时机:立即/延迟...        // 调用目的:扩展功能、权限控制、过滤...        before();        super.request();        after();    }    /**     * 对目标对象服务的具体定制,可以是一群方法、类、服务...     */    public void before() {        System.out.println("called before request().");    }    public void after() {        System.out.println("called after request().");    }}
我们常用的cglib在实现动态代理时候,用的即是这种方式,将动态生成的代理类作为被调用类的子类,以获得访问权限。

4.2 动态代理

5. 适用场景

5.1 远程代理

要访问的资源在远端,通过代理封装远程资源的访问,屏蔽操作的复杂性。调用者调用代理对象如同调用本地对象一样,对调用者透明。远程代理可以作为分布式RPC的实现思路,比如:分布式服务框架Dubbo通过创建本地Service来代理远程服务,实现透明化的远程方法调用,即是一种远程代理的实现。

5.2 虚拟代理

“以小见大”、”懒加载”,对于创建或者加载消耗性能较大的资源,可以先创建一个消耗较小的代理,等到真正需要调用资源的时候,再通过代理进行资源的创建和加载,实现”按需加载”。代理可以是资源的”缩略图”,也可以是资源的一部分。

5.3 保护代理

实现资源访问权限控制,通过在代理层增加权限判断,保护资源的安全性。可以作为权限控制的实现思路。

5.4 缓存代理

代理对被调用这的结果进行缓存,从而降低反复调用带来的性能消耗。比如:通过spring-cache将查询结果缓存到内存中,对于相同条件的调用,直接访问缓存,提高查询性能,便是缓存代理的实现。

5.5 同步代理

通过代理对象实现对被调用者的调用同步,防止冲突,保证并发安全;

5.6 智能引用代理

通过代理实现对象的引用计数。

5.7 日志代理

通过代理记录对象访问日志;

5.8 分布式代理

对于分布式系统,提供统一的代理实现。使得访问分布式系统和访问单点无差别。比如:twemproxy作为redis集群的代理,屏蔽了redis集群底层分片部署的复杂性,在客户端看来和直接访问redis单点无差别。

6. 权衡点

优点

  • 一定程度上降低了耦合;
  • 降低调用服务的复杂性;
  • 降低消耗,提高性能;(虚拟代理、缓存代理)
  • 提升安全性;(保护代理)

缺点

  • 相比于直接调用而言,增加了一层,增加请求耗时;
  • 实现代理本身较为复杂;

7. 应用案例

1)AOP(Aspect Oriented Programming,面向切面的编程),通过预编译技术或者运行时动态生成字节码技术实现对原有类/对象能力的增强。通常分为”静态代理”和”动态代理”两类,”静态”和”动态”是根据生成AOP代理类的时机进行的划分,对应到”编译时”和”运行时”两个阶段。
静态代理,就是在.java文件编译成.class文件时,通过更改.class的字节码的方式为原有类生成AOP代理,在jvm加载类文件后,字节码已经固定,不会再有改变,因而又称为编译时增强。代表就是AspectJ框架。
动态代理,是通过动态字节码生成技术,在运行时”临时”生成AOP代理对象,代理对象对应的类文件是在运行时动态编译生成的,因而又称为运行时增强。常用工具有JDK原生的动态代理,以及Cglib动态搭理。

8. 相关原则

原则 符合 违背 描述 单一职责 √ Client、RealSubject仅负责自己的业务实现,不用关注职责以外的事情; 里氏替换原则 √ Proxy关联Suject而非RealSubject,能够代理具体子类的操作; 依赖倒转原则 √ 同上,Proxy本身针对Subject抽象编程,没有针对具体的RealSubject实现编程; 接口隔离原则 √ Subject如果划分合理,可以做到接口隔离,比如屏蔽不需要代理的方法,由RealSubject在子类中实现; 最小知识原则 √ 同上,Subject如果划分合理,仅暴露需要上层知道的服务,则符合; 开闭原则 √ 修改具体的RealSubject,对上层不可见;如需要新增服务,可以通过实现新的RealSubject来扩展,故符合; 合成复用原则 √ Proxy调用RealSubject,使用IOC方式,属于关联关系,故符合;

9. 相关模式

装饰模式,状态模式;

10. 问题思考

1)代理模式和装饰模式、状态模式有何区别?
2)静态代理和动态代理有何异同?
3)JDK原生动态代理与cglib动态代理有何异同?
4)为什么被final修饰的方法不能够被重写?

参考资料

Spring AOP 实现原理—-AspectJ与CGLIB介绍
JVM即时编译(JIT)