引言
在软件开发中,代理模式是一种常见的设计模式。它通过引入一个中间层(代理对象),来控制对目标对象的访问,从而在不修改原有代码的基础上,增加额外的功能(如日志、权限校验、事务管理等)。Java中实现代理模式主要有两种方式:静态代理 和动态代理。本文将通过通俗易懂的讲解和完整的代码示例,带你彻底搞懂它们的原理、优缺点及适用场景。
一、静态代理
1.1 什么是静态代理?
静态代理是指在编译期 就已经确定代理类与目标类的关系。代理类由程序员手动编写(或通过工具生成),并且必须和目标类实现相同的接口。在程序运行前,代理类的.class文件就已经存在。
1.2 静态代理的实现
假设我们有一个支付场景:支付宝(Alipay)提供转账功能。现在我们需要在转账前后添加身份验证功能,但不能修改支付宝原有的代码。这时我们可以创建一个静态代理类。
步骤:
-
定义一个转账接口
Transfer。 -
目标类
Alipay实现该接口。 -
代理类
AlipayStaticProxy也实现Transfer接口,并在内部持有Alipay对象的引用,在调用真实方法前后添加增强逻辑。
代码示例
java
// 转账接口
interface Transfer {
void transfer(String from, String to, double amount);
}
// 目标类:支付宝
class Alipay implements Transfer {
@Override
public void transfer(String from, String to, double amount) {
System.out.println(from + " 向 " + to + " 转账 " + amount + " 元");
}
}
// 静态代理类:为支付宝添加验证功能
class AlipayStaticProxy implements Transfer {
private Alipay alipay; // 持有目标对象
public AlipayStaticProxy(Alipay alipay) {
this.alipay = alipay;
}
// 模拟身份验证
private void authenticate(String from, String to) {
System.out.println("验证 " + from + " 和 " + to + " 的身份信息");
}
@Override
public void transfer(String from, String to, double amount) {
authenticate(from, to); // 前置增强
alipay.transfer(from, to, amount); // 调用真实方法
// 可以添加后置处理
}
}
// 客户端测试
public class StaticProxyDemo {
public static void main(String[] args) {
Alipay alipay = new Alipay();
AlipayStaticProxy proxy = new AlipayStaticProxy(alipay);
proxy.transfer("小明", "小红", 100);
}
}
输出:
java
验证 小明 和 小红 的身份信息
小明 向 小红 转账 100.0 元
1.3 静态代理的优缺点
优点:
-
实现简单,易于理解。
-
运行效率高,因为没有反射等额外开销。
缺点:
-
代码冗余:每个目标类都需要一个对应的代理类,如果系统中接口较多,会导致类爆炸。
-
维护困难:接口中增加一个方法,所有代理类和目标类都需要同步修改。
-
不够灵活:代理逻辑与目标类绑定,无法动态切换增强行为。
二、动态代理
2.1 什么是动态代理?
动态代理是指在运行期动态生成代理类,无需为每个目标类手动编写代理类。Java提供了两种常见的动态代理方式:
-
JDK动态代理 :基于接口,使用
java.lang.reflect.Proxy和InvocationHandler。 -
CGLIB动态代理:基于子类,通过生成目标类的子类来实现代理(本文重点讲解JDK动态代理)。
2.2 JDK动态代理的核心组件
-
InvocationHandler接口 :代理对象的方法调用都会被转发到该接口的invoke方法中,我们在这里编写增强逻辑。 -
Proxy类 :提供静态方法newProxyInstance,用于动态生成代理对象。
2.3 动态代理的实现(基于实际业务场景)
沿用上面的支付场景,但这次我们有多个支付方式:支付宝(ZFB)和微信(WX),它们各自有独立的接口。我们希望用一个通用的代理处理器来为它们添加相同的身份验证功能。
代码示例
我们使用你提供的代码结构(略作整理):
接口定义:
java
// 支付宝转账接口
interface ZZ {
void zz(String A, String B, double money); // 为了简洁,用double代替BigDecimal
}
// 微信转账接口
interface WXZZ {
void wxzz(String A, String B, double money);
}
目标类实现:
java
// 支付宝目标类
class ZFB implements ZZ {
@Override
public void zz(String A, String B, double money) {
System.out.println(A + " 向 " + B + " 支付了 " + money + " 元");
}
}
// 微信目标类
class WX implements WXZZ {
@Override
public void wxzz(String A, String B, double money) {
System.out.println(A + " 向 " + B + " 支付了 " + money + " 元");
}
}
通用代理处理器(InvocationHandler实现):
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
class MT implements InvocationHandler {
private Object target; // 目标对象
public MT(Object target) {
this.target = target;
}
// 增强逻辑:身份验证
private void authenticate(String A, String B) {
System.out.println("验证 " + A + " 和 " + B + " 的身份");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强:验证身份
if (args != null && args.length >= 2) {
authenticate((String) args[0], (String) args[1]);
}
// 调用目标方法
Object result = method.invoke(target, args);
// 后置处理(如果有)
return result; // 返回真实方法的返回值
}
// 获取代理对象
public Object getProxyInstance() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
}
客户端调用:
java
public class XM {
public static void main(String[] args) {
// 代理支付宝
MT mt1 = new MT(new ZFB());
ZZ zfbProxy = (ZZ) mt1.getProxyInstance();
zfbProxy.zz("小明", "小王", 100);
System.out.println("--------------------");
// 代理微信
MT mt2 = new MT(new WX());
WXZZ wxProxy = (WXZZ) mt2.getProxyInstance();
wxProxy.wxzz("小明", "小王", 200);
}
}
输出:
java
验证 小明 和 小王 的身份
小明 向 小王 支付了 100.0 元
--------------------
验证 小明 和 小王 的身份
小明 向 小王 支付了 200.0 元
2.4 动态代理的底层原理
当我们调用 Proxy.newProxyInstance 时,JDK会在运行时动态生成一个代理类(如 $Proxy0),它:
-
继承
Proxy类,并实现指定的接口(如ZZ)。 -
在静态代码块中通过反射获取所有接口方法的
Method对象。 -
每个接口方法的实现内部,都调用
InvocationHandler.invoke方法,并将当前方法、参数传递进去。
生成的代理类大致结构如下(反编译后):
java
public final class $Proxy0 extends Proxy implements ZZ {
private static Method m1, m2, m3, ...;
static {
// 初始化 Method 对象
m3 = Class.forName("ZZ").getMethod("zz", String.class, String.class, Double.TYPE);
// ...
}
public $Proxy0(InvocationHandler h) {
super(h);
}
public void zz(String A, String B, double money) {
super.h.invoke(this, m3, new Object[]{A, B, money});
}
}
2.5 动态代理的优缺点
优点:
-
代码复用性强 :一个
InvocationHandler可以代理多个目标类。 -
灵活性高:可以在运行时动态决定是否代理、如何增强。
-
符合开闭原则:新增目标类无需修改代理逻辑,只需传入新对象即可。
缺点:
-
性能开销:反射调用比直接调用略慢,但现代JVM已优化,通常可忽略。
-
必须基于接口:目标类必须实现至少一个接口,否则无法使用JDK动态代理(此时可考虑CGLIB)。
三、静态代理 vs 动态代理
| 对比维度 | 静态代理 | 动态代理(JDK) |
|---|---|---|
| 代理类生成时机 | 编译期(手动编写) | 运行期(动态生成字节码) |
| 代码量 | 每个目标类都需要一个代理类,可能产生大量代码 | 一个处理器可代理多个目标类,代码精简 |
| 灵活性 | 低,行为固定 | 高,可通过配置动态改变增强逻辑 |
| 性能 | 较高(无反射) | 稍低(存在反射开销,但可接受) |
| 对目标类的要求 | 只需实现同一接口 | 必须实现接口(JDK动态代理的限制) |
| 适用场景 | 代理类数量少、功能固定、对性能要求极高 | 需要灵活增强、大量目标类、AOP框架等 |
四、总结
代理模式是Java开发者必须掌握的核心技术之一。静态代理 简单直接,适合小型系统或性能敏感的模块;动态代理则通过运行时生成代理类,极大地提高了代码的复用性和系统的可扩展性,是Spring AOP、MyBatis等主流框架的基础。
通过本文的讲解和代码示例,希望你能理解两种代理的实现原理,并根据实际需求灵活选择。如果你对CGLIB动态代理也感兴趣,可以自行查阅资料,其原理与JDK动态代理类似,但通过生成子类来实现代理,无需接口。