设计模式之【代理模式】
设计原则是指导我们代码设计的一些经验总结,也就是“心法”;面向对象就是我们的“武器”;设计模式就是“招式”。
以心法为基础,以武器运用招式应对复杂的编程问题。
我:妹啊,怎么我看你最近都很忙的样子?不是换了个轻松点的工作嘛?
表妹:是啊,工作是轻松点了,但是上下班太远了,想买辆二手车代步,所以我现在在做功课。
我:我看看...哇,还挺认真的,花了不少时间吧?
表妹:是啊,以前对这方面了解比较少,所以现在要做足功课才敢去买。
我:我有个同学是做这方面代理的,我让他帮你搞定。
表妹:哇,这样我就省事多啦~
你看,这不就是我们设计模式中的【代理模式】嘛?
为什么要用代理模式?
-
中介隔离作用
在一些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介作用,其特征是代理类和委托类实现相同的接口。
-
不违背开闭原则的前提下,增加功能
代理类除了是客户类和委托类的中介外,我们还可以通过代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息,过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。
代理类并不真正实现服务,而是通过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公关的服务。例如加入缓存、日志等功能,我们就可以使用代理类来完成,而没必要修改已经封装好的委托类。
按照代理创建的日期来进行分类的话,可以分为静态代理和动态代理。
静态代理:由程序员创建或特定工具自动生成源代码,再对其进行编译。在编译运行之前,代理类.class文件就已经被创建,代理类和委托类的关系在运行前就确定。
动态代理:动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定的。
静态代理是如何实现的?
代理实现的前提是,如何能够获取到委托类的调用?答案是有组合和继承两种方式。
组合:与委托类实现相同的接口,然后向代理类传入委托类的引用从而调用到目标函数。
继承:继承委托类,重写目标函数,然后通过super调用到目标函数。
我们先来看看用组合方式实现的。就举表妹买二手车的例子来说。
第一步:创建买车接口:
1 public interface IBuyCar {
2 void buyCar()
3 }
第二步:表妹要买车,实现买车的接口
1 public class BiaoMeiBuyCar implements IBuyCar {
2 @Override
3 public void buyCar() {
4 System.out.println("交易二手车");
5 }
6 }
第三步:找代理来帮表妹完成这件事
1 public class BiaoMeiBuyCarProxy implements IBuyCar {
2 private IBuyCar buyCar;
3
4 public BiaoMeiBuyCarProxy (final IBuyCar buyCar) {
5 this.buyCar = buyCar;
6 }
7
8 @Override
9 public void buyCar() {
10 // 代理除了帮忙交易外,还帮忙办理前后手续
11 System.out.println("交易前手续办理");
12 buyCar.buyCar(); // 交易
13 System.out.println("交易后手续办理");
14 }
15 }
1 IBuyCar buyCar = new BiaoMeiBuyCarProxy (new BiaoMeiBuyCar());
因为委托类和代理类实现相同的接口,是基于接口而非实现编程,所以,将BiaoMeiBuyCar类对象替换为BiaoMeiBuyCarProxy类对象,不需要改动太多代码。
大家发现没有,基于组合方式的静态代理模式跟装饰器模式很像。
是的,对于装饰器模式来说,装饰者和被装饰者都实现一个接口;对代理模式来说,代理类和委托类也都实现同一个接口。不论我们使用哪一种模式,都可以很容易地在真实对象的方法前面或后面加上自定义的方法。
这里我们先简单看一下两者的区别,另外一篇我们再仔细分析这两种设计模式的区别哈。
代理模式注重的是对对象的某一功能的流程把控和辅助,它可以控制对象做某些事,重点是为了借用对象的功能完成某一流程,而非对象功能如何。
装饰器模式注重的是对对象功能的扩展,不关心外界如何调用,只注重对对象功能加强,装饰后还是对象本身。
但是,如果委托类并没有定义接口,并且委托类代码并不是我们开发维护的(比如,它来自一个第三方的类库),我们也没办法直接修改原始类,给它重新定义一个接口。在这种情况下,我们该如何实现静态代理模式呢?
对于这种情况,我们一般采用继承的方式。让代理类继承委托类,然后扩展附加功能。
1 public class BiaoMeiBuyCar {
2 public void buyCar() {
3 System.out.println("交易二手车");
4 }
5 }
6 •
7 public class BiaoMeiBuyCarProxy extends BiaoMeiBuyCar {
8 public void buyCar() {
9 // 代理除了帮忙交易外,还替表妹办理好前后手续
10 System.out.println("交易前手续办理");
11 super.buyCar(); // 交易
12 System.out.println("交易后手续办理");
13 }
14 }
15 •
16 public class Demo {
17 public static void main(String[] args) {
18 // 1、找代理对象
19 BiaoMeiBuyCar biaomeiBuyCarProxy = new BiaoMeiBuyCarProxy();
20 // 2、代理负责帮表妹搞定一切
21 biaomeiBuyCarProxy .buyCar();
22 }
23 }
你看,不管是基于组合方式还是基于继承方式的静态代理模式,一方面,都需要在代理类中,将原始类中的所有方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑。另一方面,如果要添加附加功能的类不止一个,我们就需要针对每个类都创建一个代理类。那必然会增加类的个数,增加了代码维护成本。而且,每个代理类中的代码都有点像模板式的“重复”代码,也增加了不必要的开发成本。
这时候,动态代理就派上用场了。
动态代理
在动态代理中,我们不再需要手动创建代理类,只需要编写一个动态处理器就可以了。
JDK动态代理
真正的代理对象由JDK在运行时帮我们动态的创建。
第一步:创建买车接口:
1 public interface IBuyCar {
2 void buyCar()
3 }
第二步:表妹要买车,实现买车的接口
1 public class BiaoMeiBuyCar implements IBuyCar {
2 @Override
3 public void buyCar() {
4 System.out.println("交易二手车");
5 }
6 }
第三步:实现动态处理器
1 import java.lang.reflect.InvocationHandler;
2 import java.lang.reflect.Method;
3
4 public class DynamicProxyHandler implements InvocationHandler {
5 private Object object;
6
7 public DynamicProxyHandler(final Object object) {
8 this.object = object;
9 }
10
11 @Override
12 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
13 System.out.println("交易前手续办理");
14 Object result = method.invoke(object, args);
15 System.out.println("交易后手续办理");
16 return result;
17 }
18 }
1 public class Demo {
2 public static void main(String[] args) {
3 IBuyCar biaomeiBuyCar = new BiaoMeiBuyCar();
4 IBuyCar buyCarProxy = (IBuyCar)Proxy.newProxyInstance(IBuyCar.class.getClassLoader(), new Class[]{IBuyCar.class}, new DynamicProxyHandler(biaomeiBuyCar));
5 buyCarProxy.buyCar();
6 }
7 }
但是,JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,就需要CGLib了。
CGLIB动态代理
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术,拦截所有父类方法的调用,顺势织入横切逻辑。
但因为采用的是继承,所以不能对fianl修饰的类进行代理。
第一步:创建CGLib代理类。
1 package wei.proxy.impl;
2
3 import net.sf.cglib.proxy.Enhancer;
4 import net.sf.cglib.proxy.MethodInterceptor;
5 import net.sf.cglib.proxy.MethodProxy;
6
7 import java.lang.reflect.Method;
8
9 public class CglibProxy implements MethodInterceptor {
10 private Object target;
11 public Object getInstance(final Object target) {
12 this.target = target;
13 Enhancer enhancer = new Enhancer();
14 enhancer.setSuperclass(this.target.getClass());
15 enhancer.setCallback(this);
16 return enhancer.create();
17 }
18
19 public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
20 System.out.println("交易前手续办理");
21 Object result = methodProxy.invokeSuper(object, args);
22 System.out.println("交易后手续办理");
23 return result;
24 }
25 }
1 public class Demo {
2 public static void main(String[] args){
3 IBuyCar biaomeiBuyCar = new BiaoMeiBuyCar();
4 CglibProxy cglibProxy = new CglibProxy();
5 BiaoMeiBuyCar buyCarCglibProxy = (BiaoMeiBuyCar) cglibProxy.getInstance(biaomeiBuyCar);
6 buyCarCglibProxy.buyCar();
7 }
8 }
代理模式的优点
1、代理模式能将代理对象与真实被调用的目标对象隔离;
2、一定程度上降低了系统的耦合度,扩展性好;
3、可以起到保护目标对象的作用;
4、可以对目标对象的功能增强;
代理模式的缺点
1、代理模式会造成系统设计中类的数量增加;
2、在客户端与目标对象之间增加一个代理对象,会造成请求处理速度变慢;
3、增加了系统的复杂度。
代理模式的应用场景
代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。
我们将这些附加功能与业务功能解耦,放到代理类统一处理,让程序员只需要关注业务方面的开发。
除此之外,代理模式还可以用在RPC、缓存等应用场景,
总结
代理模式其实就是在访问对象时,引入一定程度的间接性,因为这种间接性,可以附加多种用途。
代理就是真实对象的代表。
参考资料
https://www.cnblogs.com/yanggb/p/10952843.html
极客时间专栏《设计模式之美》