深入解析代理模式:静态代理、JDK 动态代理和 CGLIB 的全方位对比!

news/2024/11/13 15:16:53/

代理模式(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;}@Overridepublic void deposit(double amount) {balance += amount;System.out.println("Deposited " + amount + ", new balance is " + balance);}@Overridepublic void withdraw(double amount) {if (amount <= balance) {balance -= amount;System.out.println("Withdrew " + amount + ", new balance is " + balance);} else {System.out.println("Insufficient funds.");}}@Overridepublic 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;}@Overridepublic void deposit(double amount) {if (userRole.equals("Admin")) {realBankAccount.deposit(amount);} else {System.out.println("Access denied: You don't have permission to deposit.");}}@Overridepublic void withdraw(double amount) {if (userRole.equals("Admin")) {realBankAccount.withdraw(amount);} else {System.out.println("Access denied: You don't have permission to withdraw.");}}@Overridepublic 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;}@Overridepublic void deposit(double amount) {balance += amount;System.out.println("Deposited " + amount + ", new balance is " + balance);}@Overridepublic void withdraw(double amount) {if (amount <= balance) {balance -= amount;System.out.println("Withdrew " + amount + ", new balance is " + balance);} else {System.out.println("Insufficient funds.");}}@Overridepublic 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;}@Overridepublic 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包,或者使用 MavenGradle 导入 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;}@Overridepublic 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 动态代理的优缺点

优点

  1. 支持无接口类的代理:CGLIB 能够代理普通类,不要求目标类必须实现接口,这比 JDK 动态代理更灵活。
  2. 性能高:相比 JDK 动态代理,CGLIB 生成的代理类性能更高,尤其是在大量调用代理方法的场景下。
  3. 透明性:客户端无需修改,代理类的生成是透明的。

缺点

  1. 不能代理 final 类和方法:由于 CGLIB 代理是通过生成子类实现的,因此无法代理 final 类和 final 方法。
  2. 依赖第三方库:CGLIB 是一个外部库,增加了项目的依赖复杂度。

总结:CGLIB 动态代理的特点

  • 代理普通类:CGLIB 允许代理没有实现接口的类,这比 JDK 动态代理更加灵活。
  • 通过继承实现代理:CGLIB 生成目标类的子类,并重写目标方法来实现代理。
  • 应用场景广泛:CGLIB 动态代理适用于需要代理普通类、且调用频繁的场景。

🎯 Spring AOP 代理机制

Spring AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架中的核心特性之一,它通过代理对象来对目标对象的方法进行拦截,在方法执行前后加入额外的逻辑,如日志记录、权限验证、事务管理等。

Spring AOP 中使用了两种代理机制:

  1. JDK 动态代理(基于接口的代理)
  2. CGLIB 动态代理(基于子类的代理)

使用的代理类型

  • JDK 动态代理:当目标类实现了接口时,Spring AOP 默认使用 JDK 动态代理来生成代理对象。
  • CGLIB 动态代理:当目标类没有实现接口时,Spring AOP 会使用 CGLIB 来生成代理对象。

Spring AOP 如何选择代理机制

Spring 选择代理的规则
  1. 如果目标对象实现了接口,Spring AOP 默认使用 JDK 动态代理
  2. 如果目标对象没有实现接口,Spring AOP 使用 CGLIB 动态代理
  3. 可以强制使用 CGLIB:即使目标对象实现了接口,也可以通过配置来强制使用 CGLIB 代理。

Spring AOP 动态代理应用场景

Spring AOP 的代理机制广泛应用于以下场景:

  1. 事务管理:通过代理对象,在方法执行时自动管理事务的开启和提交。
  2. 日志记录:在方法执行前后自动添加日志记录。
  3. 权限控制:通过代理对象,控制用户是否有权限调用某些方法。
  4. 缓存机制:在方法执行前,先检查缓存,如果缓存中存在结果则直接返回,否则执行目标方法并将结果存入缓存

三种代理类型的对比

下表详细对比了 静态代理JDK 动态代理CGLIB 动态代理 的不同点。

对比维度静态代理JDK 动态代理CGLIB 动态代理
实现方式手动创建代理类通过 Proxy 类和 InvocationHandler 动态生成通过继承目标类,使用字节码生成子类
是否需要接口是,需要代理类和目标类实现相同接口是,必须代理实现了接口的类否,不需要接口,直接代理类本身
代理类生成时间编译时生成,代码已确定运行时动态生成运行时动态生成
实现复杂度需要手动编写代理类,代码重复较简单,自动生成代理类复杂度较高,需要字节码生成库
方法调用方式代理类直接调用目标类方法代理对象通过 InvocationHandler 反射调用目标方法通过字节码技术生成子类,直接调用父类方法
代理性能性能较好,方法直接调用性能较差,基于反射调用,反射开销大性能较高,生成的子类直接调用父类方法
代理对象结构代理对象和目标对象有相同的接口代理对象和目标对象实现相同接口代理对象是目标类的子类
应用场景适用于代理数量少、简单的场景适用于需要代理实现接口的场景适用于没有实现接口的类,或者需要大量代理的场景
是否可代理 final否,无法代理 final否,无法代理 final
优点实现简单,直观灵活,可以代理接口,易于扩展可代理普通类,性能较高,适用于没有接口的类
缺点需要为每个目标类手动编写代理类,代码冗余只能代理接口,基于反射调用性能较低无法代理 final 类,依赖外部库,配置较复杂

总结:三种代理的特点和适用场景

  1. 静态代理
    • 特点:代理类由开发者手动编写,固定且已确定,代码较多、可维护性较低。
    • 适用场景:代理类少、功能固定的场景,简单、容易实现。
  2. JDK 动态代理
    • 特点:只能代理实现了接口的类,通过反射调用目标方法,代理类在运行时生成,性能相对较低。
    • 适用场景:目标对象实现了接口,尤其是需要动态代理多个接口的场景,如日志记录、权限控制等。
  3. CGLIB 动态代理
    • 特点:不要求目标类必须实现接口,使用字节码生成技术生成目标类的子类,性能高于 JDK 动态代理,但无法代理 final 类和 final 方法。
    • 适用场景:目标类没有实现接口,且代理调用频繁时使用,尤其适合对普通类的代理

http://www.ppmy.cn/news/1528275.html

相关文章

网络安全与国家安全的关系

网络安全与国家安全密切相关&#xff0c;网络安全是国家安全的重要组成部分。网络安全不仅关系到国家的政治安全、经济安全、文化安全、社会安全、军事安全等领域&#xff0c;还直接影响着国家的总体安全观。在信息化时代&#xff0c;网络空间已成为继陆、海、空、天之后的第五…

无人机视角应急救援(人)数据集

无人机视角应急救援&#xff08;人&#xff09;&#xff0c;两个数据集 part1&#xff0c;使用DJI Phantom 4A拍摄&#xff0c;分辨率为19201080像素。山区场景&#xff0c;图像中人员姿势分为站立、坐着、躺着、行走、奔跑。共1981张图像6500个不同姿势的标记&#xff0c; par…

Pandas中df常用方法介绍

目录 常用方法df.columnsdf.indexdf.valuesdf.Tdf.sort_index()df.sort_values() 案例 常用方法 df.columns df.columns 是 Pandas 中 DataFrame 对象的一个属性&#xff0c;用于获取 DataFrame 中的列标签&#xff08;列名&#xff09;。 基本语法如下&#xff1a; df.col…

卡牌抽卡机小程序:市场发展下的创新

今年以来&#xff0c;卡牌成为了行业中的黑马&#xff0c;在国内迅速流行&#xff0c;成为消费者的心头好。小小的卡牌创下了百亿的市场规模&#xff0c;发展前景巨大&#xff01; 不过&#xff0c;随着卡牌市场的不断增长&#xff0c;市场发展也需要进行创新。线上抽卡机小程…

线阵相机的参数选型计算

一、要求 如果测量物体的宽度为1800mm,精度1毫米、运动速度25000mm/s。 1. 相机分辨率的确定 幅宽与像素的关系&#xff1a;客户要求的幅宽是1800毫米&#xff0c;精度是1毫米。理论上&#xff0c;如果每个像素对应1毫米&#xff0c;那么相机至少需要1800个像素来覆盖整个幅…

FGT-KVM虚拟机安装步骤

根据FortiOS release note要求&#xff0c;KVM要求的安装条件CentOS 6.4 (qemu 0.12.1) or later。 1.安装CentOS 6.5 CentOS-6.5-x86_64-LiveDVD.iso 下载种子: 2.是否支持虚拟机 egrep ‘(vmx|svm)’ --coloralways /proc/cpuinfo [rootlocalhost ~]# egrep ‘(vmx|svm)’…

rabbitmq容器化部署

目录 需求 容器化部署rabbitmq服务 部署服务 验证及访问服务 rabbitmq配置LTS 服务验证 rabbitmq配置集群 部署集群 1、创建一个存放配置文件的目录 2、创建配置文件 3、部署各个节点 集群验证 需求 容器化部署rabbitmq服务 基础版本 系统ubuntu 24&#xff0c;docke…

2025秋招LLM大模型多模态面试题(六)-KV缓存

目录 为什么Transformer推理需要KV缓存?KV缓存的具体实现 没有缓存的情况下使用缓存的情况下KV缓存在解码中的阶段划分 Prefil阶段Decoding阶段KV缓存的存储类型及显存占用计算KV缓存的局限与优化策略 超长文本与复杂模型场景下的瓶颈量化方案的应用量化方案的副作用与优化方法…