张裕 发表于 2024-7-30 17:45:04

【设计模式】署理模式详解

1.简介

署理模式是常用的Java设计模式,该模式的特点是署理类与委托类共享雷同的接口。署理类重要负责预处理消息、过滤消息、将消息转发给委托类,并在过后处理消息等。署理类与委托类之间通常存在关联关系,一个署理类对象与一个委托类对象关联。署理类对象自己不真正实现服务,而是通过调用委托类对象的相关方法来提供特定的服务。
署理模式重要包括以下脚色:


[*]抽象主题(Subject):界说署理类和委托类(RealSubject)的共同接口。这个接口规定了署理类和委托类必须实现的方法,署理类可以通过这个接口来调用委托类的方法。
[*]真实主题(RealSubject):实现抽象主题,界说委托类的操作。它包罗了实际的业务逻辑,是客户端实际需要调用的对象。
[*]署理类(Proxy):实现抽象主题,持有对委托类的引用,并在其方法被调用时进行控制。署理类在调用委托类的方法前后可以添加一些额外的功能,如日志记载、权限控制、变乱处理等。
https://img-blog.csdnimg.cn/img_convert/80ef3d392b64d48b7d399950e9803e2b.png
2.静态署理

静态署理: 在编译时期确定署理类和目标类的关系,署理类和目标类都要实现同一个接口。
界说一个简单的例子:如果一个租客需要租房子,他可以直接通过房东(委托类) 去租房,也可以颠末中介(署理类) 去租房。房东(realsubject)和中介(proxy)都需要实现subject接口实现房子出租。

[*]确定接口具体活动
首先创建一个Person接口。这个接口是房东和中介的共同接口,租房活动可以被中介署理。
public interface Person {
    // 出租房子
    void hire();
}

[*]编写委托类业务逻辑
创建一个委托类,实现subject接口,并编写业务逻辑
public class Landlord implements Person{
        // 房东直售
    @Override
    public void hire() {
      System.out.println("出租,收款1000元");
    }
}

[*]署理类增强方法
创建一个署理类,同样实现subject接口,对委托类的方法进行增强
public class Agency implements Person{

    private final Landlord landlord;

    public Agency(Landlord landlord) {
      this.landlord = landlord;
    }
        // 中介出租,额外收取费用
    @Override
    public void hire() {
      System.out.println("开始办理租房手续");
      landlord.hire();
      System.out.println("额外收取中介费200元");
    }
}

[*]测试类使用署理对象
public class Main {
    public static void main(String[] args) {
      // 获取代理对象
      Landlord landlord = new Landlord();
      Agency agency = new Agency(landlord);
      // 使用代理方法
      agency.hire();
    }
}
输出结果如下,可以发现对方法进行了增强
https://img-blog.csdnimg.cn/img_convert/42bebf639e4f78e8cf186226639c491b.png
3.动态署理

动态署理: 在程序运行时动态生成署理类(subject的实现类)。
相比于静态署理, 动态署理的上风在于其较高的灵活性和代码复用性。同一个动态署理处理器可以署理多个目标对象,而静态署理则需要创建大量的署理类。
在Java中,可以通过JDK和CGLIB实现动态署理。
3.1.JDK动态署理

实现原理

JDK动态署理:在java的java.lang.reflect包下提供了Proxy类和InvocationHandler接口,使用这两个类和接口,可以在运行时动态生成指定接口的实现类。
   Proxy类就是用来创建一个署理对象的类,在JDK动态署理中我们需要使用其newProxyInstance方法。
public static Object newProxyInstance(
    ClassLoader loader,
    Class<?>[] interfaces,
    InvocationHandler h);
这个方法的作用就是创建一个署理类对象,它接收以下三个参数:


[*]loader:一个ClassLoader对象,指定哪个ClassLoader将加载生成的署理类。
[*]interfaces:一个Interface对象数组,界说署理对象实现的一组接口,署理类可以调用这些接口中声明的全部方法。
[*]h:一个InvocationHandler对象,指定署理对象的方法调用将关联到哪个InvocationHandler对象,由它处理实际的方法调用。
   InvocationHandler接口提供了一个invoke方法,当署理对象调用方法时,invoke方法会被调用。通过实现这个接口,可以在方法调用前后添加自界说逻辑。
/**
    * proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
    * method:我们所要调用某个对象真实的方法的Method对象
    * args:指代代理对象方法传递的参数
    */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
代码实现


[*]确定接口具体活动
这里我们设计两个接口
public interface HouseServiceA {
    // 出租房子
    void hire();
}
public interface HouseServiceB {
    // 转租房子
    void sublet();
}

[*]编写委托类业务逻辑
委托类实现这两个接口,而且界说具体的业务逻辑
public class HouseServiceImpl implements HouseServiceA, HouseServiceB {

    @Override
    public void hire() {
      System.out.println("出租房子");
    }

    @Override
    public void sublet() {
      System.out.println("转租房子");
    }
}

[*]编写署理工厂代码
署理工厂负责在运行时动态生成署理类,需要实现InvocationHandler接口重写invoke方法来做方法增强,使用Proxy类创建署理对象。
/**
* JDK动态代理实现InvocationHandler接口
*/
public class HouseFactory implements InvocationHandler {
    private Object target;

    //定义获取代理对象的方法(将目标对象传入进行代理)
    public Object getJDKProxy(Object target){
      //为目标对象target赋值
      this.target = target;
      //JDK动态代理只能针对实现了接口的类进行代理,因此需要传递接口的class
      return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class<?>[]{HouseServiceA.class, HouseServiceB.class},
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("JDK动态代理开始");
      // 调用invoke方法,result存储该方法的返回值
      Object result = method.invoke(target, args);
      System.out.println("JDK动态代理结束");
      return result;
    }
}

[*]测试类使用署理对象
在实际使用时,只需要将工厂类生成的署理对象转为需要的署理类,即可实现同时署理多个接口的方法。
public class Main {
    public static void main(String[] args) {
      // 创建代理类工厂
      HouseFactory factory = new HouseFactory();
      // 动态生成接口A的代理类
      HouseServiceA houseProxyA = (HouseServiceA) factory.getJDKProxy(new HouseServiceImpl());
      houseProxyA.hire();
      System.out.println("=====================");
      // 动态生成接口B的代理类
      HouseServiceB houseProxyB = (HouseServiceB) factory.getJDKProxy(new HouseServiceImpl());
      houseProxyB.sublet();
    }
}
返回结果:
https://img-blog.csdnimg.cn/img_convert/7f6842102ea12b646fe436a82dcf25ae.png
3.2.CGLIB动态署理

实现原理

CGLIB动态署理:依靠于ASM下的Enhancer类和MethodInterceptor接口,可以在运行时动态生成目标类的子类。
   Enhancer类是用来创建署理对象的类。在CGLIB动态署理中,我们需要使用其create方法。
public class Enhancer {
    public Object create();
    // 其他方法
}
这个方法的作用是创建一个署理类对象,通常还需要设置以下几个属性:


[*]setSuperclass:设置被署理的目标类,CGLIB通过生成目标类的子类来实现署理。
[*]setCallback:设置回调接口,用于处理署理对象的方法调用。
   MethodInterceptor接口提供了一个intercept方法,当署理对象调用方法时,intercept方法会被调用。通过实现这个接口,可以在方法调用前后添加自界说逻辑。
/**
    * obj: 代理对象
    * method: 被代理的方法
    * args: 方法的参数
    * proxy: 用于调用父类方法的代理
    */
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
与JDK动态署理不同,CGLIB署理不需要目标类实现接口。CGLIB通过生成目标类的子类并重写方法来实现署理,因此它可以署理没有实现接口的类。
代码实现


[*]首先导入依靠
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

[*]编写委托类业务逻辑(无需实现接口)
public class HouseServiceImpl {

    public void hire() {
      System.out.println("出租房子");
    }

    public void sublet() {
      System.out.println("转租房子");
    }
}

[*]测试类使用署理对象
public class Main {
    public static void main(String[] args) {
      HouseFactory factory = new HouseFactory();
      HouseServiceImpl cglibProxy = (HouseServiceImpl) factory.getCglibProxy(new HouseServiceImpl());
      cglibProxy.hire();
      System.out.println("=====================");
      cglibProxy.sublet();
    }
}
返回结果:
https://img-blog.csdnimg.cn/img_convert/94d48194406e13eaf85e74f2dfeb75d6.png
4.总结

静态署理

实现方式:


[*]由程序员显式编写署理类。署理类在编译期确定,编译前就存在署理类的字节码文件。
[*]需要实现与目标对象雷同的接口,且在署理类中显式调用目标对象的方法。
优点:


[*]结构简单,容易明白。
缺点:


[*]每增加一个接口,都需要编写对应的署理类,代码量大,维护资本高。静态署理类在编译期生成,灵活性差。
JDK动态署理

实现方式:


[*]使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。
[*]署理类在运行时动态生成,不需要显式编写署理类。
优点:


[*]署理类在运行时生成,增加了代码的灵活性和可维护性。
缺点:


[*]只能署理实现了接口的类,不能署理没有实现接口的类。
CGLIB动态署理

实现方式:


[*]使用CGLIB(Code Generation Library),依靠ASM字节码生成框架。
[*]署理类在运行时动态生成,不需要显式编写署理类。
优点:


[*]不要求目标类实现接口,可以署理普通的类。
[*]性能通常比JDK动态署理更高,尤其在署理大量方法调用时更为明显。
缺点:


[*]不能署理final类和final方法。
   实用场景:


[*] 静态署理:需要手动编写署理类,实用于简单的场景,但不敷灵活,维护资本高。
[*] JDK动态署理:实用于实现了接口的类,署理类在运行时生成,灵活性高,但只能署理接口。
[*] CGLIB动态署理:实用于没有实现接口的类,性能优于JDK动态署理,但不能署理final类和final方法,且使用复杂度稍高。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【设计模式】署理模式详解