代理模式 (Proxy Pattern)是一种结构型设计模式,它提供了对象的替身 ,即代理对象来控制对实际对象 的访问。通过代理对象,可以在不修改目标对象的情况下,扩展 或控制 其功能。例如,代理模式可以用于延迟加载 、权限控制 、日志记录等场景。
🎯 核心要点:
- 代理对象:代理模式通过代理对象替代实际对象进行控制,代理对象和实际对象实现相同的接口。
- 控制访问:代理对象可以控制客户端与实际对象的交互,甚至对客户端的请求进行预处理或后处理。
- 延迟初始化:代理对象可以在需要的时候才创建实际对象,节省资源。
UML类图
Subject
:这是接口,定义了代理对象和实际对象都要实现的公共接口,包含方法 request()
。
RealSubject
:实现 Subject
接口的类,表示真正执行操作的对象。
Proxy
:同样实现了 Subject
接口,代理 RealSubject
对象,控制对 RealSubject
的访问
静态代理
静态代理是指在编译期就已经确定了代理类。我们必须手动创建代理类,并明确代理哪个对象。代理类与被代理类实现相同的接口,通过代理类来控制对实际对象的访问。
静态代理案例:银行账户管理
假设我们有一个银行账户管理系统,用户通过 BankAccount
类管理账户余额,BankAccountProxy
作为代理类,添加了权限控制功能,只有拥有特定权限的用户才能执行账户操作
。
案例场景:
- 实际对象 :
BankAccount
负责执行账户的具体操作(如查询余额)。 - 代理对象 :
BankAccountProxy
负责控制对BankAccount
的访问,确保只有权限用户可以操作账户。
静态代理代码实现
Step 1: 定义接口
java
// Subject 接口
public interface BankAccount {
void deposit(double amount);
void withdraw(double amount);
double getBalance();
}
Step 2: 实现具体的银行账户类
java
// RealSubject 实现类
public class RealBankAccount implements BankAccount {
private double balance;
public RealBankAccount(double initialBalance) {
this.balance = initialBalance;
}
@Override
public void deposit(double amount) {
balance += amount;
System.out.println("Deposited " + amount + ", new balance is " + balance);
}
@Override
public void withdraw(double amount) {
if (amount <= balance) {
balance -= amount;
System.out.println("Withdrew " + amount + ", new balance is " + balance);
} else {
System.out.println("Insufficient funds.");
}
}
@Override
public double getBalance() {
return balance;
}
}
Step 3: 实现代理类
java
// Proxy 类
public class BankAccountProxy implements BankAccount {
private RealBankAccount realBankAccount;
private String userRole;
public BankAccountProxy(RealBankAccount realBankAccount, String userRole) {
this.realBankAccount = realBankAccount;
this.userRole = userRole;
}
@Override
public void deposit(double amount) {
if (userRole.equals("Admin")) {
realBankAccount.deposit(amount);
} else {
System.out.println("Access denied: You don't have permission to deposit.");
}
}
@Override
public void withdraw(double amount) {
if (userRole.equals("Admin")) {
realBankAccount.withdraw(amount);
} else {
System.out.println("Access denied: You don't have permission to withdraw.");
}
}
@Override
public double getBalance() {
return realBankAccount.getBalance();
}
}
Step 4: 测试代理类
java
public class Client {
public static void main(String[] args) {
// 创建真实对象和代理对象
RealBankAccount realAccount = new RealBankAccount(1000);
BankAccount proxyAccount = new BankAccountProxy(realAccount, "User");
// 测试代理访问
proxyAccount.deposit(500); // 访问受限
proxyAccount.withdraw(300); // 访问受限
// 以 Admin 身份访问
BankAccount adminAccount = new BankAccountProxy(realAccount, "Admin");
adminAccount.deposit(500); // 成功存款
adminAccount.withdraw(300); // 成功取款
}
}
输出结果:
java
Access denied: You don't have permission to deposit.
Access denied: You don't have permission to withdraw.
Deposited 500.0, new balance is 1500.0
Withdrew 300.0, new balance is 1200.0
解释:
- 权限控制 :
BankAccountProxy
控制了对RealBankAccount
的访问,只有拥有 Admin 权限的用户才能操作账户。 - 灵活扩展 :通过代理类,我们可以在不修改
RealBankAccount
的前提下,灵活地添加权限控制功能。
动态代理(JDK 动态代理)
动态代理是在运行时 动态生成代理类,而不是在编译时确定。动态代理可以通过反射机制自动生成代理对象,而无需手动编写代理类。
动态代理案例:银行账户管理(JDK 动态代理)
在 动态代理 中,代理类是在运行时动态生成的。Java 提供了 java.lang.reflect.Proxy
类和 InvocationHandler
接口来实现动态代理。
案例场景:
和静态代理案例类似,我们还是使用 BankAccount
管理账户,但是通过 JDK 动态代理 来动态生成代理类,代理类控制用户的操作权限,并记录日志
。
动态代理代码实现
Step 1: 定义接口(与静态代理相同)
java
// Subject 接口
public interface BankAccount {
void deposit(double amount);
void withdraw(double amount);
double getBalance();
}
Step 2: 实现具体的银行账户类(与静态代理相同)
java
// RealSubject 实现类
public class RealBankAccount implements BankAccount {
private double balance;
public RealBankAccount(double initialBalance) {
this.balance = initialBalance;
}
@Override
public void deposit(double amount) {
balance += amount;
System.out.println("Deposited " + amount + ", new balance is " + balance);
}
@Override
public void withdraw(double amount) {
if (amount <= balance) {
balance -= amount;
System.out.println("Withdrew " + amount + ", new balance is " + balance);
} else {
System.out.println("Insufficient funds.");
}
}
@Override
public double getBalance() {
return balance;
}
}
Step 3: 实现 InvocationHandler
接口
InvocationHandler
是动态代理的核心,通过 invoke()
方法拦截对目标对象的方法调用。
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class BankAccountInvocationHandler implements InvocationHandler {
private Object target;
private String userRole;
public BankAccountInvocationHandler(Object target, String userRole) {
this.target = target;
this.userRole = userRole;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (userRole.equals("Admin")) {
System.out.println("Admin access granted.");
return method.invoke(target, args); // 调用目标对象的方法
} else {
System.out.println("Access denied: You don't have permission to " + method.getName());
return null;
}
}
}
Step 4: 动态代理测试
java
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
// 创建真实对象
RealBankAccount realAccount = new RealBankAccount(1000);
// 创建动态代理对象
BankAccount proxyAccount = (BankAccount) Proxy.newProxyInstance(
realAccount.getClass().getClassLoader(),
new Class[]{BankAccount.class},
new BankAccountInvocationHandler(realAccount, "User")
);
// 测试代理访问
proxyAccount.deposit(500); // 访问受限
proxyAccount.withdraw(300); // 访问受限
// 以 Admin 身份访问
BankAccount adminAccount = (BankAccount) Proxy.newProxyInstance(
realAccount.getClass().getClassLoader(),
new Class[]{BankAccount.class},
new BankAccountInvocationHandler(realAccount, "Admin")
);
adminAccount.deposit(500); // 成功存款
adminAccount.withdraw(300); // 成功取款
}
}
输出结果:
Access denied: You don't have permission to deposit
Access denied: You don't have permission to withdraw
Admin access granted.
Deposited 500.0, new balance is 1500.0
Admin access granted.
Withdrew 300.0, new balance is 1200.0
解释:
- 运行时生成代理类 :通过
Proxy.newProxyInstance()
方法,动态生成代理类。 - 权限控制:动态代理可以在运行时灵活地进行权限控制,且不需要手动创建代理类。
CGLIB 动态代理
通过生成目标类的子类,并重写其中的方法来实现代理。它是在运行时生成的字节码,所以可以代理普通类和接口。代理类实际上是目标类的子类,并且会调用父类的方法。
- 依赖 :需要导入
cglib
相关的库。 - 限制 :由于 CGLIB 是通过继承实现的,所以不能代理
final
类 或**final
方法**,因为这些无法被继承和重写。
CGLIB 依赖导入
在项目中,你需要下载CGLIB相关的所有JAR包,或者使用 Maven 或 Gradle 导入 cglib
依赖
Jar包下载地址 :相关JAR点击下载
Maven 依赖:
java
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
Gradle 依赖:
implementation 'cglib:cglib:3.3.0'
案例场景:银行账户管理(CGLIB 动态代理)
我们将基于前面的银行账户管理系统,使用 CGLIB 实现动态代理,控制用户操作权限并记录日志
。
场景:
- 实际对象 :
BankAccount
是一个普通类,没有实现任何接口。 - 代理对象:使用 CGLIB 动态生成代理类,实现权限控制和日志功能
CGLIB 动态代理代码实现
Step 1: 创建 BankAccount
类
不再实现接口,这是一个普通类,CGLIB 可以代理这个类。
java
// RealSubject 实现类,普通类,没有实现接口
public class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
public void deposit(double amount) {
balance += amount;
System.out.println("Deposited " + amount + ", new balance is " + balance);
}
public void withdraw(double amount) {
if (amount <= balance) {
balance -= amount;
System.out.println("Withdrew " + amount + ", new balance is " + balance);
} else {
System.out.println("Insufficient funds.");
}
}
public double getBalance() {
return balance;
}
}
Step 2: 创建 MethodInterceptor
实现类
MethodInterceptor
是 CGLIB 代理的核心,通过重写 intercept()
方法来拦截目标类的方法调用
java
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class BankAccountMethodInterceptor implements MethodInterceptor {
private String userRole;
public BankAccountMethodInterceptor(String userRole) {
this.userRole = userRole;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (userRole.equals("Admin")) {
System.out.println("Admin access granted.");
return proxy.invokeSuper(obj, args); // 调用父类的原方法
} else {
System.out.println("Access denied: You don't have permission to " + method.getName());
return null;
}
}
}
Step 3: 使用 Enhancer
动态生成代理类
CGLIB 使用 Enhancer
类来生成代理对象,Enhancer
会生成一个目标类的子类,并将方法调用委托给 MethodInterceptor
。
java
import net.sf.cglib.proxy.Enhancer;
public class Client {
public static void main(String[] args) {
// 创建 Enhancer 对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(BankAccount.class); // 设置代理目标类
enhancer.setCallback(new BankAccountMethodInterceptor("User")); // 设置拦截器
// 创建代理对象
BankAccount proxyAccount = (BankAccount) enhancer.create(new Class[]{double.class}, new Object[]{1000.0});
// 测试代理访问
proxyAccount.deposit(500); // 访问受限
proxyAccount.withdraw(300); // 访问受限
// 以 Admin 身份访问
enhancer.setCallback(new BankAccountMethodInterceptor("Admin"));
BankAccount adminAccount = (BankAccount) enhancer.create(new Class[]{double.class}, new Object[]{1000.0});
adminAccount.deposit(500); // 成功存款
adminAccount.withdraw(300); // 成功取款
}
}
输出结果:
Access denied: You don't have permission to deposit
Access denied: You don't have permission to withdraw
Admin access granted.
Deposited 500.0, new balance is 1500.0
Admin access granted.
Withdrew 300.0, new balance is 1200.0
解释:
- 动态生成代理类 :通过
Enhancer
类,动态生成了BankAccount
类的代理对象。 - 权限控制 :
MethodInterceptor
控制了对目标方法的调用,只有具有 Admin 权限的用户才能执行操作。 - 日志功能:代理类在执行目标方法前,打印日志信息。
CGLIB 动态代理的优缺点
优点:
- 支持无接口类的代理:CGLIB 能够代理普通类,不要求目标类必须实现接口,这比 JDK 动态代理更灵活。
- 性能高:相比 JDK 动态代理,CGLIB 生成的代理类性能更高,尤其是在大量调用代理方法的场景下。
- 透明性:客户端无需修改,代理类的生成是透明的。
缺点:
- 不能代理
final
类和方法 :由于 CGLIB 代理是通过生成子类实现的,因此无法代理final
类和final
方法。 - 依赖第三方库:CGLIB 是一个外部库,增加了项目的依赖复杂度。
总结:CGLIB 动态代理的特点
- 代理普通类:CGLIB 允许代理没有实现接口的类,这比 JDK 动态代理更加灵活。
- 通过继承实现代理:CGLIB 生成目标类的子类,并重写目标方法来实现代理。
- 应用场景广泛:CGLIB 动态代理适用于需要代理普通类、且调用频繁的场景。
🎯 Spring AOP 代理机制
Spring AOP (Aspect-Oriented Programming,面向切面编程)是 Spring 框架中的核心特性之一,它通过代理对象来对目标对象的方法进行拦截,在方法执行前后加入额外的逻辑,如日志记录、权限验证、事务管理等。
Spring AOP 中使用了两种代理机制:
- JDK 动态代理(基于接口的代理)
- CGLIB 动态代理(基于子类的代理)
使用的代理类型
- JDK 动态代理 :当目标类实现了接口时,Spring AOP 默认使用 JDK 动态代理来生成代理对象。
- CGLIB 动态代理 :当目标类没有实现接口时,Spring AOP 会使用 CGLIB 来生成代理对象。
Spring AOP 如何选择代理机制
Spring 选择代理的规则:
- 如果目标对象实现了接口 ,Spring AOP 默认使用 JDK 动态代理。
- 如果目标对象没有实现接口 ,Spring AOP 使用 CGLIB 动态代理。
- 可以强制使用 CGLIB:即使目标对象实现了接口,也可以通过配置来强制使用 CGLIB 代理。
Spring AOP 动态代理应用场景
Spring AOP 的代理机制广泛应用于以下场景:
- 事务管理:通过代理对象,在方法执行时自动管理事务的开启和提交。
- 日志记录:在方法执行前后自动添加日志记录。
- 权限控制:通过代理对象,控制用户是否有权限调用某些方法。
- 缓存机制:在方法执行前,先检查缓存,如果缓存中存在结果则直接返回,否则执行目标方法并将结果存入缓存
三种代理类型的对比
下表详细对比了 静态代理 、JDK 动态代理 和 CGLIB 动态代理 的不同点。
对比维度 | 静态代理 | JDK 动态代理 | CGLIB 动态代理 |
---|---|---|---|
实现方式 | 手动创建代理类 | 通过 Proxy 类和 InvocationHandler 动态生成 |
通过继承目标类,使用字节码生成子类 |
是否需要接口 | 是,需要代理类和目标类实现相同接口 | 是,必须代理实现了接口的类 | 否,不需要接口,直接代理类本身 |
代理类生成时间 | 编译时生成,代码已确定 | 运行时动态生成 | 运行时动态生成 |
实现复杂度 | 需要手动编写代理类,代码重复 | 较简单,自动生成代理类 | 复杂度较高,需要字节码生成库 |
方法调用方式 | 代理类直接调用目标类方法 | 代理对象通过 InvocationHandler 反射调用目标方法 |
通过字节码技术生成子类,直接调用父类方法 |
代理性能 | 性能较好,方法直接调用 | 性能较差,基于反射调用,反射开销大 | 性能较高,生成的子类直接调用父类方法 |
代理对象结构 | 代理对象和目标对象有相同的接口 | 代理对象和目标对象实现相同接口 | 代理对象是目标类的子类 |
应用场景 | 适用于代理数量少、简单的场景 | 适用于需要代理实现接口的场景 | 适用于没有实现接口的类,或者需要大量代理的场景 |
是否可代理 final 类 |
是 | 否,无法代理 final 类 |
否,无法代理 final 类 |
优点 | 实现简单,直观 | 灵活,可以代理接口,易于扩展 | 可代理普通类,性能较高,适用于没有接口的类 |
缺点 | 需要为每个目标类手动编写代理类,代码冗余 | 只能代理接口,基于反射调用性能较低 | 无法代理 final 类,依赖外部库,配置较复杂 |
总结:三种代理的特点和适用场景
- 静态代理 :
- 特点:代理类由开发者手动编写,固定且已确定,代码较多、可维护性较低。
- 适用场景:代理类少、功能固定的场景,简单、容易实现。
- JDK 动态代理 :
- 特点:只能代理实现了接口的类,通过反射调用目标方法,代理类在运行时生成,性能相对较低。
- 适用场景:目标对象实现了接口,尤其是需要动态代理多个接口的场景,如日志记录、权限控制等。
- CGLIB 动态代理 :
- 特点 :不要求目标类必须实现接口,使用字节码生成技术生成目标类的子类,性能高于 JDK 动态代理,但无法代理
final
类和final
方法。 - 适用场景:目标类没有实现接口,且代理调用频繁时使用,尤其适合对普通类的代理
- 特点 :不要求目标类必须实现接口,使用字节码生成技术生成目标类的子类,性能高于 JDK 动态代理,但无法代理