• 金三银四面试题(二十一):代理模式知多少?


    代理模式在软件开发中是一个非常重要的设计模式之一。它提供了一种灵活的方式来控制和管理对象的访问,并且可以在访问对象前后执行额外的操作。

    什么是代理模式

    代理模式是一种结构型设计模式,其目的是为其他对象提供一种代理以控制对这个对象的访问。代理对象通常充当客户端与真实对象之间的中介,客户端通过代理对象访问真实对象,从而可以在访问前后进行一些额外的控制、管理或者操作。

    代理模式通常包括以下角色:

    1. 抽象主题(Subject):声明真实对象和代理对象的共同接口,这样客户端可以通过这个接口访问真实对象或代理对象。

    2. 真实主题(Real Subject):定义了代理所代表的真实对象,客户端最终想要访问的对象。

    3. 代理(Proxy):保存一个引用使得代理可以访问实体,并且提供一个与真实主题相同的接口,这样代理就可以替代真实主题。

    代理模式通常有以下几种常见的应用场景:

    • 远程代理(Remote Proxy):用于代表不同地址空间中的对象。通过远程代理,客户端可以访问远程服务器上的对象。

    • 虚拟代理(Virtual Proxy):用于代表比较消耗资源的对象。虚拟代理延迟加载(lazy initialization)真实对象,直到客户端真正需要访问它为止。

    • 保护代理(Protection Proxy):用于控制对对象的访问。保护代理可以根据调用者的身份、权限等条件决定是否允许访问对象的某些方法。

    • 缓存代理(Cache Proxy):用于提高系统的性能。缓存代理在第一次访问真实对象时,将结果缓存起来,以后的访问可以直接返回缓存的结果,避免重复执行相同的操作。

    代理模式能够有效地提供额外的控制和管理,同时也能够提供更好的性能和资源利用。

    例子

    生活中的例子

    • 黄牛卖火车票:没有流行网络购票的年代是很喜欢找黄牛买火车票的,因为工作忙的原因,没时间去买票,然后就托黄牛给你买张回家过年的火车票。这个过程中黄牛就是代理人,火车票就是被代理的对象。

    • 婚姻介绍所:婚姻介绍所的工作人员,搜集单身人士信息,婚介所的工作人员为这个单身人士找对象,这个过程也是代理模式的生活案例。对象就是被代理的对象。

    动态代理和静态代理

    动态代理和静态代理都是代理模式的两种实现方式,它们都用于控制对对象的访问,并在访问对象前后执行额外的操作,但它们的实现方式和特点有所不同。

    1. 静态代理

      • 静态代理是在编译时期就已经确定了代理类和真实类的关系,代理类是通过手动编码实现的。
      • 在静态代理中,代理类和真实类通常都要实现同一个接口,代理类中包含了对真实对象的引用,并且在方法调用前后执行额外的操作。
      • 静态代理的一个缺点是如果要代理的对象过多,可能会导致代理类的数量过多,增加了代码的复杂性和维护成本。
    2. 动态代理

      • 动态代理是在运行时动态生成代理类的方式实现的,它允许在运行时动态地创建代理对象。
      • 在动态代理中,代理类是由代理工厂根据指定的接口和方法来动态生成的,并在方法调用时通过方法拦截器来执行额外的操作。
      • 动态代理的一个优点是可以减少代码量,因为代理类是动态生成的,不需要手动编写大量的代理类。同时,动态代理也更加灵活,可以代理任意的接口和类,不受静态代理中接口的限制。

    总的来说,静态代理在编译时期确定了代理关系,适用于一些固定且较少变化的场景,而动态代理在运行时动态生成代理类,适用于需要灵活处理对象的访问和操作的场景。在 Java 中,JDK 动态代理和 CGLIB 动态代理是两种常见的动态代理实现方式,它们都能够实现在运行时动态生成代理类,并在方法调用时执行额外的操作。

    spring中的动态代理

    在 Spring 框架中,动态代理是 AOP(面向切面编程)的一个重要实现方式之一。AOP 允许开发者在程序运行期间动态地将额外的逻辑(称为切面)织入到现有的代码中,而动态代理正是实现这一功能的一种手段。

    Spring 中的动态代理主要基于 JDK 动态代理和 CGLIB(Code Generation Library,代码生成库)两种技术。下面分别简要介绍这两种动态代理的实现方式:

    1. JDK 动态代理

      • JDK 动态代理是通过反射机制在运行时动态创建代理类的方式实现的。它要求目标对象必须实现一个或多个接口,然后基于这些接口创建代理对象。
      • 在 Spring 中,当目标对象实现了接口时,Spring 会使用 JDK 动态代理来创建代理对象。代理对象会实现目标对象所实现的接口,并在方法调用时委托给 InvocationHandler 来处理。
    2. CGLIB 动态代理

      • CGLIB 动态代理是通过继承目标对象并重写其方法的方式实现的。它不要求目标对象必须实现接口,可以对任意的类进行代理。
      • 在 Spring 中,当目标对象没有实现接口时,Spring 会使用 CGLIB 动态代理来创建代理对象。代理对象会继承目标对象,并在方法调用时通过方法拦截器(MethodInterceptor)来处理。

    Spring 中动态代理的实现主要依赖于 Proxy 类和 ProxyFactory 工厂类。Spring 提供了两种方式来创建代理对象:基于接口的 JDK 动态代理和基于类的 CGLIB 动态代理。在使用 Spring AOP 时,开发者可以选择合适的代理方式来实现切面功能,并通过配置文件或注解来指定切面的切入点和通知类型。这样就可以在程序运行期间动态地将切面织入到目标对象的方法调用中,实现各种横切关注点的功能,例如日志记录、性能监控、事务管理等。

    代码实现

    静态代理

    // 定义接口
    interface Calculator {
        int add(int a, int b);
    }
    
    // 实现接口的类
    class CalculatorImpl implements Calculator {
        @Override
        public int add(int a, int b) {
            return a + b;
        }
    }
    
    // 代理类
    class CalculatorProxy implements Calculator {
        private final Calculator target;
    
        public CalculatorProxy(Calculator target) {
            this.target = target;
        }
    
        @Override
        public int add(int a, int b) {
            // 在方法调用前执行额外的操作
            System.out.println("Before method invocation");
    
            // 调用真实对象的方法
            int result = target.add(a, b);
    
            // 在方法调用后执行额外的操作
            System.out.println("After method invocation");
    
            return result;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            // 创建真实对象
            Calculator calculator = new CalculatorImpl();
    
            // 创建代理对象
            Calculator proxy = new CalculatorProxy(calculator);
    
            // 使用代理对象调用方法
            int result = proxy.add(3, 4);
            System.out.println("Result: " + result);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    动态代理

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    // 定义接口
    interface Calculator {
        int add(int a, int b);
    }
    
    // 实现接口的类
    class CalculatorImpl implements Calculator {
        @Override
        public int add(int a, int b) {
            return a + b;
        }
    }
    
    // 实现 InvocationHandler 接口来处理方法调用
    class CalculatorInvocationHandler implements InvocationHandler {
        private final Calculator target;
    
        public CalculatorInvocationHandler(Calculator target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 在方法调用前执行额外的操作
            System.out.println("Before method invocation");
    
            // 调用真实对象的方法
            Object result = method.invoke(target, args);
    
            // 在方法调用后执行额外的操作
            System.out.println("After method invocation");
    
            return result;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            // 创建真实对象
            Calculator calculator = new CalculatorImpl();
    
            // 创建动态代理对象
            Calculator proxy = (Calculator) Proxy.newProxyInstance(
                    Calculator.class.getClassLoader(),
                    new Class[]{Calculator.class},
                    new CalculatorInvocationHandler(calculator)
            );
    
            // 使用代理对象调用方法
            int result = proxy.add(3, 4);
            System.out.println("Result: " + result);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
  • 相关阅读:
    Tensorboard入门使用及显示空白/乱码/没有数据的问题
    代码随想录算法训练营Day35 | 贪心算法(4/6) LeetCode 860.柠檬水找零 406.根据身高重建队列 452. 用最少数量的箭引爆气球
    java 异步发展史 Runnable Callable Future CompletableFuture
    Nacos集群和持久化配置(重要)
    基于SSM的航班订票管理系统的设计与实现
    幂等性(防重复提交)
    基于Python+OpenCV+SVM车牌识别系统-车牌预处理系统
    Centos-7静默安装Oracle-11gr2
    【2023年数学建模国赛】D题解题思路
    量子化学新突破:提前预测分子基态中的化学键断裂时间
  • 原文地址:https://blog.csdn.net/weixin_45700531/article/details/137891801