设计模式之【代理模式】

设计原则是指导我们代码设计的一些经验总结,也就是“心法”;面向对象就是我们的“武器”;设计模式就是“招式”。

以心法为基础,以武器运用招式应对复杂的编程问题。

我:妹啊,怎么我看你最近都很忙的样子?不是换了个轻松点的工作嘛?

表妹:是啊,工作是轻松点了,但是上下班太远了,想买辆二手车代步,所以我现在在做功课。

我:我看看...哇,还挺认真的,花了不少时间吧?

表妹:是啊,以前对这方面了解比较少,所以现在要做足功课才敢去买。

我:我有个同学是做这方面代理的,我让他帮你搞定。

表妹:哇,这样我就省事多啦~

你看,这不就是我们设计模式中的【代理模式】嘛?


为什么要用代理模式?

  • 中介隔离作用

    在一些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介作用,其特征是代理类和委托类实现相同的接口。

  • 不违背开闭原则的前提下,增加功能

    代理类除了是客户类和委托类的中介外,我们还可以通过代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息,过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。

    代理类并不真正实现服务,而是通过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公关的服务。例如加入缓存、日志等功能,我们就可以使用代理类来完成,而没必要修改已经封装好的委托类。

按照代理创建的日期来进行分类的话,可以分为静态代理和动态代理。

静态代理:由程序员创建或特定工具自动生成源代码,再对其进行编译。在编译运行之前,代理类.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

极客时间专栏《设计模式之美》

https://www.cnblogs.com/daniels/p/8242592.html

posted @ 2022-03-14 11:02  Gopher大威  阅读(159)  评论(0编辑  收藏  举报