设计模式之【代理模式】

【设计模式之【代理模式】】设计原则是指导我们代码设计的一些经验总结,也就是“心法”;面向对象就是我们的“武器”;设计模式就是“招式”。
以心法为基础,以武器运用招式应对复杂的编程问题。

我:妹啊,怎么我看你最近都很忙的样子?不是换了个轻松点的工作嘛?
表妹:是啊,工作是轻松点了,但是上下班太远了,想买辆二手车代步,所以我现在在做功课。
我:我看看...哇,还挺认真的,花了不少时间吧?
表妹:是啊,以前对这方面了解比较少,所以现在要做足功课才敢去买。
我:我有个同学是做这方面代理的,我让他帮你搞定。
表妹:哇,这样我就省事多啦~
你看,这不就是我们设计模式中的【代理模式】嘛?
为什么要用代理模式?
  • 中介隔离作用
    在一些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介作用,其特征是代理类和委托类实现相同的接口。
  • 不违背开闭原则的前提下,增加功能
    代理类除了是客户类和委托类的中介外,我们还可以通过代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息,过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。
    代理类并不真正实现服务,而是通过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公关的服务。例如加入缓存、日志等功能,我们就可以使用代理类来完成,而没必要修改已经封装好的委托类。
按照代理创建的日期来进行分类的话,可以分为静态代理和动态代理。
静态代理:由程序员创建或特定工具自动生成源代码,再对其进行编译。在编译运行之前,代理类.class文件就已经被创建,代理类和委托类的关系在运行前就确定。
动态代理:动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定的。
静态代理是如何实现的?
代理实现的前提是,如何能够获取到委托类的调用?答案是有组合和继承两种方式。
组合:与委托类实现相同的接口,然后向代理类传入委托类的引用从而调用到目标函数。
继承:继承委托类,重写目标函数,然后通过super调用到目标函数。
我们先来看看用组合方式实现的。就举表妹买二手车的例子来说。
第一步:创建买车接口:
1 public interface IBuyCar { 2void buyCar() 3 }

第二步:表妹要买车,实现买车的接口
1 public class BiaoMeiBuyCar implements IBuyCar { 2@Override 3public void buyCar() { 4System.out.println("交易二手车"); 5} 6 }

第三步:找代理来帮表妹完成这件事
1 public class BiaoMeiBuyCarProxy implements IBuyCar { 2private IBuyCar buyCar; 3 4public BiaoMeiBuyCarProxy (final IBuyCar buyCar) { 5this.buyCar = buyCar; 6} 7 8@Override 9public void buyCar() { 10// 代理除了帮忙交易外,还帮忙办理前后手续 11System.out.println("交易前手续办理"); 12buyCar.buyCar(); // 交易 13System.out.println("交易后手续办理"); 14} 15 }

1 IBuyCar buyCar = new BiaoMeiBuyCarProxy (new BiaoMeiBuyCar());

因为委托类和代理类实现相同的接口,是基于接口而非实现编程,所以,将BiaoMeiBuyCar类对象替换为BiaoMeiBuyCarProxy类对象,不需要改动太多代码。
大家发现没有,基于组合方式的静态代理模式跟装饰器模式很像。
是的,对于装饰器模式来说,装饰者和被装饰者都实现一个接口;对代理模式来说,代理类和委托类也都实现同一个接口。不论我们使用哪一种模式,都可以很容易地在真实对象的方法前面或后面加上自定义的方法。
这里我们先简单看一下两者的区别,另外一篇我们再仔细分析这两种设计模式的区别哈。
代理模式注重的是对对象的某一功能的流程把控和辅助,它可以控制对象做某些事,重点是为了借用对象的功能完成某一流程,而非对象功能如何。
装饰器模式注重的是对对象功能的扩展,不关心外界如何调用,只注重对对象功能加强,装饰后还是对象本身。
但是,如果委托类并没有定义接口,并且委托类代码并不是我们开发维护的(比如,它来自一个第三方的类库),我们也没办法直接修改原始类,给它重新定义一个接口。在这种情况下,我们该如何实现静态代理模式呢?
对于这种情况,我们一般采用继承的方式。让代理类继承委托类,然后扩展附加功能。
1 public class BiaoMeiBuyCar { 2public void buyCar() { 3System.out.println("交易二手车"); 4} 5 } 6 ? 7 public class BiaoMeiBuyCarProxy extends BiaoMeiBuyCar { 8public void buyCar() { 9// 代理除了帮忙交易外,还替表妹办理好前后手续 10System.out.println("交易前手续办理"); 11super.buyCar(); // 交易 12System.out.println("交易后手续办理"); 13} 14 } 15 ? 16 public class Demo { 17public static void main(String[] args) { 18// 1、找代理对象 19BiaoMeiBuyCar biaomeiBuyCarProxy = new BiaoMeiBuyCarProxy(); 20// 2、代理负责帮表妹搞定一切 21biaomeiBuyCarProxy .buyCar(); 22} 23 }

你看,不管是基于组合方式还是基于继承方式的静态代理模式,一方面,都需要在代理类中,将原始类中的所有方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑。另一方面,如果要添加附加功能的类不止一个,我们就需要针对每个类都创建一个代理类。那必然会增加类的个数,增加了代码维护成本。而且,每个代理类中的代码都有点像模板式的“重复”代码,也增加了不必要的开发成本。
这时候,动态代理就派上用场了。
动态代理
在动态代理中,我们不再需要手动创建代理类,只需要编写一个动态处理器就可以了。
JDK动态代理 真正的代理对象由JDK在运行时帮我们动态的创建。
第一步:创建买车接口:
1 public interface IBuyCar { 2void buyCar() 3 }

第二步:表妹要买车,实现买车的接口
1 public class BiaoMeiBuyCar implements IBuyCar { 2@Override 3public void buyCar() { 4System.out.println("交易二手车"); 5} 6 }

第三步:实现动态处理器
1import java.lang.reflect.InvocationHandler; 2import java.lang.reflect.Method; 3? 4public class DynamicProxyHandler implements InvocationHandler { 5private Object object; 6 7public DynamicProxyHandler(final Object object) { 8this.object = object; 9} 10 11@Override 12public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 13System.out.println("交易前手续办理"); 14Object result = method.invoke(object, args); 15System.out.println("交易后手续办理"); 16return result; 17} 18}

1 public class Demo { 2public static void main(String[] args) { 3IBuyCar biaomeiBuyCar = new BiaoMeiBuyCar(); 4IBuyCar buyCarProxy = (IBuyCar)Proxy.newProxyInstance(IBuyCar.class.getClassLoader(), new Class[]{IBuyCar.class}, new DynamicProxyHandler(biaomeiBuyCar)); 5buyCarProxy.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 { 10private Object target; 11public Object getInstance(final Object target) { 12this.target = target; 13Enhancer enhancer = new Enhancer(); 14enhancer.setSuperclass(this.target.getClass()); 15enhancer.setCallback(this); 16return enhancer.create(); 17} 18 ? 19public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { 20System.out.println("交易前手续办理"); 21Object result = methodProxy.invokeSuper(object, args); 22System.out.println("交易后手续办理"); 23return result; 24} 25 }

1 public class Demo { 2public static void main(String[] args){ 3IBuyCar biaomeiBuyCar = new BiaoMeiBuyCar(); 4CglibProxy cglibProxy = new CglibProxy(); 5BiaoMeiBuyCar buyCarCglibProxy = (BiaoMeiBuyCar) cglibProxy.getInstance(biaomeiBuyCar); 6buyCarCglibProxy.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

    推荐阅读