找回密码
 立即注册
查看: 160|回复: 0

07 设计模式自学笔记(Java)-代办代理模式Proxy[布局型模式]

[复制链接]
发表于 2024-7-15 18:00 | 显示全部楼层 |阅读模式
一、代办代理模式的本质




代办代理模式道理示意

代办代理模式的本质是通过代办代理为被代办代理的对象提供统一的接口,并在调用被代办代理对象的接口进行功能增强,如进行相应的预措置和后措置,主要特征是代办代理与被代办代理有同样的接口。也就是说,客户端不会直接与被代办代理的对象直接交互,二是通过代办代理与被代办代理对象简介交互,但最后核心的逻辑还是由被代办代理对象执行在通过代办代理返回给用户。
二、代办代理模式目的

代办代理模式其目的是:
1)屏蔽客户端对被代办代理对象的细节感知,有时候被代办代理对象不合适直接与客户端交互,或者是被代办代理对象功能太复杂直接让客户端操作很难。
2)其次,被代办代理的更新对客户端无感知,只要代办代理进行响应的改削即可。
3)最后,被代办代理的对象如果有多个,很难保证表露给客户端的接口都是一致的,这样会增加客户端的设计难度,例如需要客户端本身实现封装。
三、示例场景

假设分歧的银行提供了收款的处事接口,银行A使用的是基于XML的HTTP数据交换格式,而银行B提供的是基于JSON的数据交换格式,假设再有其它很多银行提供的是其他种类的数据交换格式,如果一个客户端要接入所有银行的收款处事,那必需实现多种数据交换格式,很麻烦。这时候,需要一个中间代办代理,代办代理向客户端提供独一一种数据交换格式的撑持,例如是protobuf,在代办代理的内部提供protobuf到其它各种数据交换格式的彼此转换,这样的话,客户端只要实现与代办代理的基于protobuf的交互即可。
四、代办代理模式的道理




代办代理模式道理-功能增强

代办代理模式的核心道理就是:
1)封装被代办代理对象的接口,并向客户端提供统一的接口;
2)在接口措置期间进行响应的功能增强,如在调用被代办代理对象的真实接口前增加前置措置,或者是在接收到被代办代理对象的接口返回后增加后置措置,最后将成果返回给客户端。
4.1 静态代办代理道理

静态代办代理在编译期间生成一个新的class(代办代理类),这个代办代理类与被代办代理类是独立的两个类,编译期间会生成对应的两个类的class。
静态代办代理要求代办代理类和被代办代理类都实现同样的接口,核心的实现思路就是在代办代理类实现接口方式的时候调用被代办代理响应的方式,并在方式前后进行其它逻辑的增强。
这种代办代理方式需要代办代理对象和方针对象实现一样的接口。
长处: * 可以在不改削方针对象的前提下扩展方针对象的功能。
错误谬误: 冗余。由于代办代理对象要实现与方针对象一致的接口,会发生过多的代办代理类。 不易维护。一旦接口增加方式,方针对象与代办代理对象都要进行改削。
4.2 动态代办代理道理

动态代办代理操作了JDK API,动态地在内存中构建代办代理对象,从而实现对被代办代理对象的代办代理功能。常用的动态代办代理有JDK动态代办代理和CGLIB动态代办代理。
静态代办代理与动态代办代理的区别主要在

  • 静态代办代理在编译时就已经实现,编译完成儿女办代理类是一个实际的class文件;
  • 动态代办代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中。
动态代办代理的特点

  • JDK动态代办代理类不需要实现接口,但是要求被代办代理类必需实现接口,否则不能使用动态代办代理;
  • CGLIB动态代办代理不需要实现接口,可以直接对类进行代办代理,但是要求类不能是Final类型的。
五、 代码实现与分析

(1)静态代办代理实现

静态代办代理的实现需要代办代理类和被代办代理类同时实现一个接口,并重写接口中相应的方式。 接口定义(IProxy):
  1. //代办代理类和被代办代理类都需要实现这个接口
  2. public interface IProxy {
  3.     //代办代理类和被代办代理类都需要重写这个方式
  4.     void pay();
  5. }
复制代码
被代办代理类(Proxyee):
  1. //被代办代理类实现IProxy并重写pay()方式
  2. public class Proxyee implements IProxy{
  3.     @Override
  4.     public void pay() {
  5.         System.out.println(”Pay through Bank A with XML.”);
  6.     }
  7. }
复制代码
代办代理类(Proxyer):
  1. //代办代理类实现IProxy并重写pay()方式
  2. public class Proxyer implements IProxy {
  3.     //类的内部下性定义一个被代办代理类的对象
  4.     private IProxy targetBank;
  5.     private String targetDataType;
  6.     //通过有参构造函数对内部的属性赋值,通过属性将被代办代理类的实例传给类的内部下性
  7.     public Proxyer(IProxy targetBank, String dataType){
  8.         this.targetBank = targetBank;
  9.         this.targetDataType = dataType;
  10.     }
  11.     //重写接口的pay()方式
  12.     @Override
  13.     public void pay() {
  14.         switch (targetDataType) {
  15.             case ”XML”:
  16.                 //对被代办代理类的方式进行前置增强,此处用打印暗示,实际开发中可以替换为其它的业务措置逻辑,如记录日志等。
  17.                 System.out.println(”Pre Data format conversion: From ProtoBuf  ---->  XML”);
  18.                 //调用被代办代理实际的pay()方式
  19.                 targetBank.pay();
  20.                 //对被代办代理类的方式进行后增强,此处用打印暗示,实际开发中可以替换为其它的业务措置逻辑,如记录日志等。
  21.                 System.out.println(”Post Data format conversion: From XML  ---->  ProtoBuf”);
  22.                 break;
  23.             case ”JSON”:
  24.                 System.out.println(”Pre Data format conversion: From ProtoBuf  ---->  JSON”);
  25.                 targetBank.pay();
  26.                 System.out.println(”Post Data format conversion: From JSON  ---->  ProtoBuf”);
  27.                 break;
  28.             default:
  29.                 throw new RuntimeException(”No this type of data format!!!”);
  30.         }
  31.     }
  32. }
复制代码
客户端:
  1. public class Main {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         Proxyee Proxyee = new Proxyee();
  4.         Proxyer aapter = new Proxyer(Proxyee, ”XML”);
  5.         Proxyer.pay();
  6.     }
  7. }
复制代码
静态代办代理中的UML类图如下:



静态代办代理UML类图

静态代办代理实现的关键点:

  • 1)代办代理类和被代办代理类同时实现一个成果并重写统一方式(需要被代办代理的方式);
  • 2)代办代理类内部创建被代办代理类的实例,在重写相应方式时,内部还是调用被代办代理类的方式,只不外是进行了相应的功能增强。
(2)动态代办代理-JDK代办代理实现

JDK动态代办代理是有JDK提供的东西类Proxy实现的,动态代办代理类是在运行时生成指定接口的代办代理类,每个代办代理实例(实现需要代办代理的接口)都有一个关联的调用措置法式对象,此对象实现了InvocationHandler,最终的业务逻辑是在InvocationHandler实现类的invoke方式上。
接口定义(IPhone):
  1. //被代办代理对象需要实现的接口和方式
  2. public interface IPhone {
  3.     public void callSomeone(String name,String number);
  4. }
复制代码
被代办代理对象(PhoneProxyee):
  1. //实现上面的接口
  2. public class PhoneProxyee implements IPhone{
  3.     String band;
  4.     int price;
  5. //重写接口中的方式
  6.     @Override
  7.     public void callSomeone(String name, String number) {
  8.         System.out.println(”Making a call to ” + name + ” to ” + number);
  9.     }
  10. }
复制代码
代办代理类(PhoneProxyer):
  1. //代办代理类实现InvocationHandler
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. public class PhoneProxyer implements InvocationHandler {
  5.     private Object obj;
  6.     public PhoneProxyer(Object obj) {
  7.         this.obj = obj;
  8.     }
  9.     //重写InvocationHandler中的invoke方式
  10.     //增强的代码逻辑就在此处定义,就是这里的打印,实际开发中可以增加相应的业务措置逻辑
  11.     /*invoke(Object proxy, Method method, Object[] args)有三个参数
  12.      * Object proxy:需要被代办代理的对象,这里在代办代理类中以属性的方式定义了,且通过有参函数实例化具体的对象
  13.      * Method method:当前正在调用的方式,这个例子里面就是callSomeone(String name, String number)这个方式
  14.      * Object[] args:被调用方式的参数,也就是callSomeone(String name, String number)方式的参数     *
  15.      */
  16.     @Override
  17.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  18.         System.out.println(”代办代理增强开始...”);
  19.         Object result = method.invoke(obj, args);
  20.         System.out.println(”代办代理增强结束...”);
  21.         return result;
  22.     }
  23. }
复制代码
客户端:
  1. public class Main {
  2.     public static void main(String[] args) {
  3.         //先创建一个被代办代理的对象实例
  4.         IPhone phoneProxyee = new PhoneProxyee();
  5.         //通过代办代理对象的有参构造方式传入被代办代理对象实例进行代办代理
  6.         InvocationHandler handler = new PhoneProxyer(phoneProxyee);
  7.         //使用Java反射包中Proxy类的newProxyInstance方式创建代办代理实例
  8.         /*newProxyInstance方式需要三个参数:
  9.          *handler.getClass().getClassLoader():代办代理对象的类加载器
  10.          * phoneProxyee.getClass().getInterfaces():动态代办代理类需要实现的接口,这个就相当于一个切入点,实现了这个接口的类对象将会被代办代理
  11.          * handler:代办代理对象实例
  12.          */        
  13.         IPhone phoneProxy = (IPhone) Proxy.newProxyInstance(
  14.                 handler.getClass().getClassLoader(),
  15.                 phoneProxyee.getClass().getInterfaces(),
  16.                 handler
  17.         );
  18.         phoneProxy.callSomeone(”Stephen”,”12345678901”);
  19.     }
  20. }
复制代码
JDK动态代办代理的UML类图如下:



JDK动态代办代理UML类图

JDK动态代办代理实现的关键点:

  • 1)被代办代理类需要实现接口以及重写接口中的方式;
  • 2)代办代理类需要实现java.lang.reflect.InvocationHandler这个接口,并重写java.lang.reflect.InvocationHandler中的invoke方式,在invoke方式中进行代办代理增强
  • 3)客户端需要通过java.lang.reflect.Proxy反射创建代办代理类的实例,通过Proxy.newProxyInstance()方式创建;
  • 4)客户端通过代办代理类调用被代办代理类的同名方式实现代办代理。
JDK动态代办代理需要被代办代理类实现接口。
(3)动态代办代理-CGLib代办代理实现

JDK动态代办代理是通过重写被代办代理对象实现的接口中的方式来实现,而CGLIB是通过担任被代办代理对象来实现,和JDK动态代办代理需要实现指定接口一样,CGLIB也要求代办代理对象必需要实现MethodInterceptor接口,并重写其独一的方式intercept。
CGLib采用了非常底层的字节码技术,其道理是通过字节码技术为一个类创建子类,并在子类中采用方式拦截的技术拦截所有父类方式的调用,顺势织入横切逻辑。(操作ASM开源包,对代办代理对象类的class文件加载进来,通过改削其字节码生成子类来措置)
注意:因为CGLIB是通过担任方针类来重写其方式来实现的,故而如果是final和private方式例无法被重写,也就是无法被代办代理。
被代办代理类(PhoneProxyee):
  1. public class PhoneProxyee {
  2.     //将要被代办代理的方式
  3.     public void callSomeone(String name, String number){
  4.         System.out.println(”Making a call to ” + name + ” to ” + number);
  5.     }
  6. }
复制代码
代办代理类(PhoneProxyer):
  1. import net.sf.cglib.proxy.Enhancer;
  2. import net.sf.cglib.proxy.MethodInterceptor;
  3. import net.sf.cglib.proxy.MethodProxy;
  4. import java.lang.reflect.Method;
  5. public class PhoneProxyer implements MethodInterceptor {
  6.     private Object obj;
  7.     //代办代理类提供创建代办代理实例的方式
  8.     public Object createProxy(Object obj){
  9.         this.obj = obj;
  10.         Enhancer enhancer = new Enhancer();
  11.         enhancer.setSuperclass(this.obj.getClass());
  12.         enhancer.setCallback(this);
  13.         return enhancer.create();
  14.     }
  15.     //代办代理类重写MethodInterceptor接口中的intercept方式
  16.     /*intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)需要四个参数
  17.      * Object o:被代办代理对象的实例
  18.      * Object[] objects:需要被代办代理的方式
  19.      * Object[] objects:被代办代理方式需要的参数
  20.      * MethodProxy methodProxy:调用被代办代理对象相应的方式。
  21.      */
  22.     @Override
  23.     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  24.         System.out.println(”CGLIB 增强代办代理开始...”);
  25.         Object result = methodProxy.invokeSuper(o,objects);
  26.         System.out.println(”CGLIB 增强代办代理结束...”);
  27.         return result;
  28.     }
  29. }
复制代码
客户端:
  1. public class Main {
  2.     public static void main(String[] args) {
  3.         PhoneProxyee phoneProxyee = new PhoneProxyee();
  4.         PhoneProxyer phoneProxyer = new PhoneProxyer();
  5.         PhoneProxyee phoneProxyeeProxy = (PhoneProxyee) phoneProxyer.createProxy(phoneProxyee);
  6.         phoneProxyeeProxy.callSomeone(”Alice”,”12356478904”);
  7.     }
  8. }
复制代码
CGLIB动态代办代理的UML类图如下:



CGLIB动态代办代理UML类图

CGLIB动态代办代理实现的关键点: 1)被代办代理类不需要实现接口,但是被代办代理类以及被代办代理的方式不能是Final修饰的; 2)代办代理类需要实现net.sf.cglib.proxy.MethodInterceptor接口,并重写intercept方式; * 3)需要引入CGLIB和ASM两个依赖包。
六、代办代理模式的优错误谬误

(1)长处


  • 分手方针对象:代办代理模式能将代办代理对象与真实被调用的方针对象分手 ;
  • 降低耦合:在必然程度上,降低了系统耦合性,扩展性好;
(2)错误谬误


  • 类个数增加:代办代理模式会造成系统中类的个数增加,比不使用代办代理模式增加了代办代理类,系统的复杂度增加; (所有的设计模式都有这个错误谬误)
  • 性能降低:尤其是动态代办代理,需要在运行时进行字节码增强,在客户端和方针对象之间,增加了一个代办代理对象,造成请求措置速度变慢;当前,新版本的动态代办代理中,JDK和CGLIB的性能基本差不多,建议一般情况下使用JDK动态代办代理,如果被代办代理的方式被调用的次数过多,可以使用CGLIB动态代办代理。此外,JDK动态代办代理需要由接口,CGLIB动态代办代理不需要,但是CGLIB动态代办代理要求被代办代理类和方式不能是Final修饰的。
(3)适用场景


  • 庇护方针对象:客户端只与代办代理类进行交互, 不会表露被代办代理类的具体细节;
  • 增强方针对象:代办代理类在方针对象的基础上, 对方针对象的功能进行增强,如统一实现日志措置;

<< 06 设计模式自学笔记(Java)-单例模式Singleton[创建型模式]
>> 08 设计模式自学笔记(Java)-适配器模式Adapter[创建型模式]

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2025-1-22 17:51 , Processed in 0.170134 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表