• GoF之代理模式


    2023.11.12

            代理模式是GoF23种设计模式之一,其作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。

            代理模式中的角色:代理类目标类代理类和目标类的公共接口(客户端在使用代理类时就像在使用目标类,不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口)。

            代理模式有动态代理和静态代理两种模式,先实现静态代理作为引入。

    静态代理

            考虑一个业务场景:某个项目已上线,并且运行正常,只是客户反馈系统有一些地方运行较慢,要求项目组对系统进行优化。于是项目负责人就下达了这个需求。首先需要搞清楚是哪些业务方法耗时较长,于是让我们统计每个业务方法所耗费的时长。

    第一种方案:直接修改Java源代码,在每个业务方法中添加统计逻辑。这种方法可以满足需求,但显然是违背了OCP开闭原则,这种方案不可取。

    第二种方案:编写一个子类继承OrderServiceImpl,在子类中重写每个方法。这种方式也可以解决需求,但是由于采用了继承的方式,导致代码之间的耦合度较高。

    第三种方案就是使用静态代理了:

    先准备一个接口类和实现类:

    1. package service;
    2. public interface OrderService {
    3. void generate();
    4. void detail();
    5. void modify();
    6. }
    1. package service;
    2. public class OrderServiceImpl implements OrderService{
    3. @Override
    4. public void generate() {
    5. try {
    6. Thread.sleep(1234);
    7. } catch (InterruptedException e) {
    8. e.printStackTrace();
    9. }
    10. System.out.println("订单已生成");
    11. }
    12. @Override
    13. public void detail() {
    14. try {
    15. Thread.sleep(2541);
    16. } catch (InterruptedException e) {
    17. e.printStackTrace();
    18. }
    19. System.out.println("订单信息如下:******");
    20. }
    21. @Override
    22. public void modify() {
    23. try {
    24. Thread.sleep(1010);
    25. } catch (InterruptedException e) {
    26. e.printStackTrace();
    27. }
    28. System.out.println("订单已修改");
    29. }
    30. }

    其中Thread.sleep()方法的调用是为了模拟操作耗时。

    采用静态代理为OrderService接口提供一个代理类。

    1. package service;
    2. public class OrderServiceProxy implements OrderService{
    3. // 目标对象
    4. private OrderService orderService;
    5. // 通过构造方法将目标对象传递给代理对象
    6. public OrderServiceProxy(OrderService orderService) {
    7. this.orderService = orderService;
    8. }
    9. @Override
    10. public void generate() {
    11. long begin = System.currentTimeMillis();
    12. // 执行目标对象的目标方法
    13. orderService.generate();
    14. long end = System.currentTimeMillis();
    15. System.out.println("耗时"+(end - begin)+"毫秒");
    16. }
    17. @Override
    18. public void detail() {
    19. long begin = System.currentTimeMillis();
    20. // 执行目标对象的目标方法
    21. orderService.detail();
    22. long end = System.currentTimeMillis();
    23. System.out.println("耗时"+(end - begin)+"毫秒");
    24. }
    25. @Override
    26. public void modify() {
    27. long begin = System.currentTimeMillis();
    28. // 执行目标对象的目标方法
    29. orderService.modify();
    30. long end = System.currentTimeMillis();
    31. System.out.println("耗时"+(end - begin)+"毫秒");
    32. }
    33. }

            这种方式的优点:符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。所以这种方案是被推荐的。

            下面在客户端中使用代理对象:

    1. import service.OrderService;
    2. import service.OrderServiceImpl;
    3. import service.OrderServiceProxy;
    4. public class Client {
    5. public static void main(String[] args) {
    6. //创建目标对象
    7. OrderService target = new OrderServiceImpl();
    8. //创建代理对象
    9. OrderService proxy = new OrderServiceProxy(target);
    10. //调用代理对象的代理方法
    11. proxy.generate();
    12. proxy.modify();
    13. proxy.detail();
    14. }
    15. }

    运行结果:

            以上就是代理模式中的静态代理,其中OrderService接口是代理类和目标类的共同接口。OrderServiceImpl是目标类。OrderServiceProxy是代理类。

            但是如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。怎么解决这个问题?动态代理可以解决。

    动态代理

            在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。

            在内存当中动态生成类的技术常见的包括:

    • JDK动态代理技术:只能代理接口。
    • CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
    • Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

    这里简单实现一下JDK动态代理技术:

            我们在静态代理的时候,除了以上一个接口和一个实现类之外,还要写一个代理类UserServiceProxy。在动态代理中UserServiceProxy代理类是可以动态生成的。这个类不需要写。我们直接写客户端程序即可:

    1. import service.OrderService;
    2. import service.OrderServiceImpl;
    3. import service.TimerInvocationHandler;
    4. import java.lang.reflect.Proxy;
    5. public class client {
    6. public static void main(String[] args) {
    7. // 第一步:创建目标对象
    8. OrderService target = new OrderServiceImpl();
    9. // 第二步:创建代理对象
    10. OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target));
    11. // 第三步:调用代理对象的代理方法
    12. // 调用代理对象的代理方法
    13. orderServiceProxy.detail();
    14. orderServiceProxy.modify();
    15. orderServiceProxy.generate();
    16. }
    17. }

    以上第二步创建代理对象是重点,其中newProxyInstance()方法有三个参数:

    • 第一个参数:类加载器。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。
    • 第二个参数:接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
    • 第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。显然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。

            接下来要写一下java.lang.reflect.InvocationHandler接口的实现类,并且实现接口中的方法,代码如下:

    1. package service;
    2. import java.lang.reflect.InvocationHandler;
    3. import java.lang.reflect.Method;
    4. public class TimerInvocationHandler implements InvocationHandler {
    5. // 目标对象
    6. private Object target;
    7. // 通过构造方法来传目标对象
    8. public TimerInvocationHandler(Object target) {
    9. this.target = target;
    10. }
    11. @Override
    12. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    13. // 目标执行之前增强。
    14. long begin = System.currentTimeMillis();
    15. // 调用目标对象的目标方法
    16. Object retValue = method.invoke(target, args);
    17. // 目标执行之后增强。
    18. long end = System.currentTimeMillis();
    19. System.out.println("耗时"+(end - begin)+"毫秒");
    20. // 一定要记得返回哦。
    21. return retValue;
    22. }
    23. }

            InvocationHandler接口中有一个方法invoke,这个invoke方法上有三个参数:

    • 第一个参数:Object proxy。代理对象。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,尽管通过这个参数来使用。
    • 第二个参数:Method method。目标方法。
    • 第三个参数:Object[] args。目标方法调用时要传的参数。

    最后,可以运行客户端程序了,运行结果:

  • 相关阅读:
    宿主机通过wlp3s0接口上网,我应该如何设置
    vivo 容器集群监控系统优化之道
    PMP最新考纲难在哪里?面对新教材的来袭,我该怎么计划考试?
    【Python】Python图形化界面库PySimpleGUI的简单使用
    仿QQ音乐(HTML+CSS)
    基于单片机技术的自动停车器的设计
    NoSQL之 Redis配置
    React-Props进阶
    review 11
    Docker网络通信原理
  • 原文地址:https://blog.csdn.net/m0_61028090/article/details/134361792