静态代理与动态代理
@toc
一:故事背景
最近在搞软考,再次进行学习设计模式,其中学习到代理模式的时候,找到的资料给出的例子是静态代理。于是想起了之前学过的动态代理,今天抽出时间,做一篇静态代理和动态代理的区分。通过区分静态代理和动态代理,深刻了解代理的重要作用。
二:静态代理
2.1 概念
静态代理是代理模式的一种实现方式,它需要程序员在编译时期手动编写代理类。在静态代理中,代理类和真实类实现相同的接口或继承相同的类,并在代理类中调用真实对象的方法。
2.2 角色
静态代理的基本结构包括三个角色:
真实对象(Real Object):被代理的对象,它实现了业务逻辑的具体功能。
代理对象(Proxy Object):代理类,它与真实对象实现相同的接口或继承相同的类。代理对象持有对真实对象的引用,并在其自身的方法中调用真实对象的方法。代理对象通常在调用真实对象的方法之前或之后执行一些额外的逻辑。
客户端(Client):通过代理对象来访问真实对象的客户端。
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 概念
- 动态代理是在运行时动态生成代理类的一种代理方式,与静态代理相对应。在动态代理中,代理类不需要程序员手动编写,而是在运行时使用Java的反射机制动态生成。
- Java提供了一个 java.lang.reflect 包来支持动态代理。动态代理通常通过 java.lang.reflect.Proxy 类来创建代理对象。
- 动态代理的基本思想是:在运行时创建一个实现特定接口的代理类对象,该代理类对象内部持有一个实现了 InvocationHandler 接口的对象,并将所有方法的调用委托给该 InvocationHandler 对象处理。
3.2 具体步骤
具体步骤如下:
- 创建一个实现 InvocationHandler 接口的类,该类负责实际的代理逻辑。
- 使用 Proxy 类的 newProxyInstance() 方法创建代理对象。该方法接收三个参数:类加载器、代理接口数组和 InvocationHandler 对象。
- 通过代理对象调用方法时,实际会转发给 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中 |
使用场景 | 静态代理适用于少量的对象或接口代理 | 动态代理适用于大量的对象或接口代理,更具灵活性 |
静态代理在编写过程中相对简单,适用于少量的对象或接口代理,但对于不同类型的对象需要编写多个代理类,缺乏灵活性。而动态代理更加灵活,可以代理不同类型的对象,无需编写多个代理类,但在运行时可能会有一些额外的开销,执行效率稍低。
五:总结&提升
本文给出了静态代理和动态代理的分析,并且给出了对应示例。通过这样的方式,大家可以更好的理解,什么是静态代理,什么是动态代理。了解概念,有助于我们更好的选择对应的方式。
- 点赞
- 收藏
- 关注作者
评论(0)