静态代理与动态代理

举报
郝老三 发表于 2023/07/25 16:10:16 2023/07/25
【摘要】 @toc 一:故事背景最近在搞软考,再次进行学习设计模式,其中学习到代理模式的时候,找到的资料给出的例子是静态代理。于是想起了之前学过的动态代理,今天抽出时间,做一篇静态代理和动态代理的区分。通过区分静态代理和动态代理,深刻了解代理的重要作用。 二:静态代理 2.1 概念静态代理是代理模式的一种实现方式,它需要程序员在编译时期手动编写代理类。在静态代理中,代理类和真实类实现相同的接口或继承相...

@toc

一:故事背景

最近在搞软考,再次进行学习设计模式,其中学习到代理模式的时候,找到的资料给出的例子是静态代理。于是想起了之前学过的动态代理,今天抽出时间,做一篇静态代理和动态代理的区分。通过区分静态代理和动态代理,深刻了解代理的重要作用。

二:静态代理

2.1 概念

静态代理是代理模式的一种实现方式,它需要程序员在编译时期手动编写代理类。在静态代理中,代理类和真实类实现相同的接口或继承相同的类,并在代理类中调用真实对象的方法。

2.2 角色

静态代理的基本结构包括三个角色:

  1. 真实对象(Real Object):被代理的对象,它实现了业务逻辑的具体功能。

  2. 代理对象(Proxy Object):代理类,它与真实对象实现相同的接口或继承相同的类。代理对象持有对真实对象的引用,并在其自身的方法中调用真实对象的方法。代理对象通常在调用真实对象的方法之前或之后执行一些额外的逻辑。

  3. 客户端(Client):通过代理对象来访问真实对象的客户端。

2.3 使用

  1. 使用静态代理的主要目的是在不修改真实对象的情况下,对其方法进行扩展或增强。代理对象可以在调用真实对象的方法前后添加一些额外的操作,例如权限控制、日志记录、性能统计等。
  2. 静态代理的一个示例是数据库连接池的实现。代理对象可以在从连接池中获取连接之前进行一些验证或日志记录,然后再将请求传递给真正的数据库连接对象进行处理。
  3. 需要注意的是,静态代理的缺点是每次需要代理不同的对象时,都需要手动编写一个代理类,导致代码的冗余。而动态代理可以在运行时动态生成代理类,更加灵活。

2.4 代码实现

2.4.1 Calculator接口

假设我们有一个接口 Calculator,定义了数学运算的方法 add 和 subtract:

public interface Calculator {
    int add(int a, int b);
    int subtract(int a, int b);
}

现在我们希望对这个接口的实现进行代理,在每次执行方法之前打印日志。

2.4.2 真实对象 CalculatorImpl

首先,我们定义一个真实对象 CalculatorImpl,实现了 Calculator 接口:

public class CalculatorImpl implements Calculator {
    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public int subtract(int a, int b) {
        return a - b;
    }
}

2.4.3 代理类 CalculatorProxy

然后,创建代理类 CalculatorProxy,它也实现了 Calculator 接口,持有对真实对象的引用,并在方法执行前后添加日志打印:

public class CalculatorProxy implements Calculator {
    private Calculator realCalculator;

    public CalculatorProxy(Calculator realCalculator) {
        this.realCalculator = realCalculator;
    }

    @Override
    public int add(int a, int b) {
        System.out.println("Before add method");
        int result = realCalculator.add(a, b);
        System.out.println("After add method");
        return result;
    }

    @Override
    public int subtract(int a, int b) {
        System.out.println("Before subtract method");
        int result = realCalculator.subtract(a, b);
        System.out.println("After subtract method");
        return result;
    }
}

2.4.4 具体使用

最后,我们可以通过代理对象来调用方法,并在控制台输出相应的日志:

public class Main {
    public static void main(String[] args) {
        Calculator realCalculator = new CalculatorImpl();
        Calculator proxyCalculator = new CalculatorProxy(realCalculator);

        int sum = proxyCalculator.add(5, 3);
        System.out.println("Sum: " + sum);

        int difference = proxyCalculator.subtract(10, 7);
        System.out.println("Difference: " + difference);
    }
}

2.4.5 输出示例

Before add method
After add method
Sum: 8
Before subtract method
After subtract method
Difference: 3

通过静态代理,我们在执行真实对象的方法前后添加了日志打印,而不需要修改真实对象的实现。这样可以方便地对真实对象的功能进行扩展或增强,例如添加性能统计、异常处理等。

三:动态代理

3.1 概念

  1. 动态代理是在运行时动态生成代理类的一种代理方式,与静态代理相对应。在动态代理中,代理类不需要程序员手动编写,而是在运行时使用Java的反射机制动态生成。
  2. Java提供了一个 java.lang.reflect 包来支持动态代理。动态代理通常通过 java.lang.reflect.Proxy 类来创建代理对象。
  3. 动态代理的基本思想是:在运行时创建一个实现特定接口的代理类对象,该代理类对象内部持有一个实现了 InvocationHandler 接口的对象,并将所有方法的调用委托给该 InvocationHandler 对象处理。

3.2 具体步骤

具体步骤如下:

  1. 创建一个实现 InvocationHandler 接口的类,该类负责实际的代理逻辑。
  2. 使用 Proxy 类的 newProxyInstance() 方法创建代理对象。该方法接收三个参数:类加载器、代理接口数组和 InvocationHandler 对象。
  3. 通过代理对象调用方法时,实际会转发给 InvocationHandler 的 invoke() 方法处理。

3.3 代码实现

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxyExample {
    public interface Calculator {
        int add(int a, int b);
        int subtract(int a, int b);
    }

    public static class CalculatorImpl implements Calculator {
        @Override
        public int add(int a, int b) {
            return a + b;
        }

        @Override
        public int subtract(int a, int b) {
            return a - b;
        }
    }

    public static class LoggingHandler implements InvocationHandler {
        private Object target;

        public LoggingHandler(Object target) {
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Before method: " + method.getName());
            Object result = method.invoke(target, args);
            System.out.println("After method: " + method.getName());
            return result;
        }
    }

    public static void main(String[] args) {
        Calculator calculator = new CalculatorImpl();
        LoggingHandler handler = new LoggingHandler(calculator);

        Calculator proxyCalculator = (Calculator) Proxy.newProxyInstance(
                Calculator.class.getClassLoader(),
                new Class<?>[]{Calculator.class},
                handler
        );

        int sum = proxyCalculator.add(5, 3);
        System.out.println("Sum: " + sum);

        int difference = proxyCalculator.subtract(10, 7);
        System.out.println("Difference: " + difference);
    }
}

在上面的示例中,我们定义了一个 Calculator 接口和其实现类 CalculatorImpl。然后,创建了一个 LoggingHandler 类,实现了 InvocationHandler 接口,负责处理代理对象的方法调用,并在方法执行前后打印日志。

最后,使用 Proxy.newProxyInstance() 方法创建代理对象 proxyCalculator,并调用其方法。实际上,这些方法的调用会委托给 LoggingHandler 的 invoke() 方法进行处理,

四:二者对比

特性 静态代理 动态代理
实现方式 需要手动编写代理类 使用反射机制在运行时动态生成代理类
编写方式 需要为每个被代理的类编写一个代理类 无需为每个被代理的类编写代理类,通过InvocationHandler处理
灵活性 灵活性较低,每次代理不同对象都需要编写代理类 更灵活,可以代理不同类型的对象,无需编写多个代理类
执行效率 编译时代理类已确定,执行效率较高 运行时动态生成代理类,可能有额外开销,执行效率稍低
扩展性 需要为每个被代理的类编写单独的代理类 可以通过统一的InvocationHandler处理多个被代理类
代码冗余 需要编写多个代理类 无需编写多个代理类,代理逻辑集中在InvocationHandler中
使用场景 静态代理适用于少量的对象或接口代理 动态代理适用于大量的对象或接口代理,更具灵活性

静态代理在编写过程中相对简单,适用于少量的对象或接口代理,但对于不同类型的对象需要编写多个代理类,缺乏灵活性。而动态代理更加灵活,可以代理不同类型的对象,无需编写多个代理类,但在运行时可能会有一些额外的开销,执行效率稍低。

五:总结&提升

本文给出了静态代理和动态代理的分析,并且给出了对应示例。通过这样的方式,大家可以更好的理解,什么是静态代理,什么是动态代理。了解概念,有助于我们更好的选择对应的方式。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。