模式定义
给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用。
主要作用
通过引入代理对象的方式来间接访问目标对象。
解决的问题
防止直接访问目标对象给系统带来的不必要复杂性。
模式原理
UML类图
模式组成
抽象主题角色(Subject):抽象主题类 的主要职责是声明 真实主题 与 代理 的共同接口方法,该类可以是接口也可以是抽象类;
真实主题角色(RealSubject):该类也被称为 被委托类 或 被代理类,该类定义了代理所表示的真实对象,是负责执行系统真正的逻辑业务对象;
代理主题角色(Proxy):也被称为 委托类 或 代理类,其内部持有 RealSubject 的引用,因此具备完全的对 RealSubject 的代理权;
模式优点
- 协调调用者和被调用者,降低了系统的耦合度。
- 代理对象作为客户端和目标对象之间的中介,起到了保护目标对象的作用。
- 职责清晰:真实主题角色 只负责实现实际的业务逻辑,无需关心其他非本职责的事务,通过后期代理的访问控制,可以扩展或缩减实际事务,符合设计模式 单一职责原则,代码简洁清晰;
- 高扩展性:真实主题角色 负责实际业务逻辑,可能经常性变动,但是由于其与 代理类 都实现了 抽象主题,因此,真实主题 的改变不会影响到代理类,符合设计模式 依赖倒置原则,里氏替换原则 和 开闭原则;
- 智能化:主要是基于动态代理的实现,可以在程序运行时生成一个合适的代理类;
模式缺点
- 由于在客户端和真实主题之间增加了代理对象,因此会造成请求的处理速度变慢;
- 实现代理模式需要额外的工作(有些代理模式的实现非常复杂),从而增加了系统实现的复杂度。
应用场景
当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过也给代理对象来间接访问;
模式示例
示例背景
背景:假设希望买一台最新的顶配Mac电脑
冲突:国内还没上,只有美国才有
解决方案:寻找代购进行购买
示例步骤
静态代理
创建抽象对象接口(Subject)
声明你(真实对象)需要让代购(代理对象)帮忙做的事(买Mac)
1 | package main.java.com.study.designPatterns.proxy.demoOne; |
创建真实对象类(RealSubject)
1 | package main.java.com.study.designPatterns.proxy.demoOne; |
创建代理对象类(Proxy)
1 | package main.java.com.study.designPatterns.proxy.demoOne; |
客户端调用
1 | package main.java.com.study.designPatterns.proxy.demoOne; |
上面代码描绘的代理模式,更加细分的话一般称为静态代理模式,为什么称为静态代理?因为代理对象需要与目标对象实现一样的接口(也就是RealSubject和ProxSubject实现buyMac接口)。
假设现在有这种情况,为了保守起见不仅找代购,还通过其他多种渠道购买,这种情况下就会有很多代理类,代理类如果太多会出现什么样的问题?
一旦接口增加方法,目标对象与代理对象都要维护,这样的话代码改起来就相当烦琐和冗余。为了解决这个问题,Java给我们提供了一种解决方式,这种解决方式大家一般称为动态代理。
动态代理
动态代理的类
1 | package main.java.com.study.designPatterns.proxy.demoOne; |
动态代理客户端调用
1 | package main.java.com.study.designPatterns.proxy.demoOne; |
虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。
CGLIB动态代理
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
CGLIB代理类
1 | package main.java.com.study.designPatterns.proxy.cglibProxy; |
CGLIB代理测试类
1 | package main.java.com.study.designPatterns.proxy.cglibProxy; |
CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。