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

代理模式(Proxy Pattern)是一种结构型设计模式,它提供了对象的替身,即代理对象来控制对实际对象的访问。通过代理对象,可以在不修改目标对象的情况下,扩展控制其功能。例如,代理模式可以用于延迟加载权限控制日志记录等场景。

🎯 核心要点:

  • 代理对象:代理模式通过代理对象替代实际对象进行控制,代理对象和实际对象实现相同的接口。
  • 控制访问:代理对象可以控制客户端与实际对象的交互,甚至对客户端的请求进行预处理或后处理。
  • 延迟初始化:代理对象可以在需要的时候才创建实际对象,节省资源。

UML类图

在这里插入图片描述

Subject:这是接口,定义了代理对象和实际对象都要实现的公共接口,包含方法 request()

RealSubject:实现 Subject 接口的类,表示真正执行操作的对象。

Proxy:同样实现了 Subject 接口,代理 RealSubject 对象,控制对 RealSubject 的访问

静态代理

静态代理是指在编译期就已经确定了代理类。我们必须手动创建代理类,并明确代理哪个对象。代理类与被代理类实现相同的接口,通过代理类来控制对实际对象的访问。

静态代理案例:银行账户管理

假设我们有一个银行账户管理系统,用户通过 BankAccount 类管理账户余额,BankAccountProxy 作为代理类,添加了权限控制功能,只有拥有特定权限的用户才能执行账户操作。

案例场景

  • 实际对象BankAccount 负责执行账户的具体操作(如查询余额)。
  • 代理对象BankAccountProxy 负责控制对 BankAccount 的访问,确保只有权限用户可以操作账户。

静态代理代码实现

Step 1: 定义接口
// Subject 接口
public interface BankAccount {void deposit(double amount);void withdraw(double amount);double getBalance();
}
Step 2: 实现具体的银行账户类
// 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: 实现代理类
// 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: 测试代理类
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); // 成功取款}
}

输出结果

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: 定义接口(与静态代理相同)
// Subject 接口
public interface BankAccount {void deposit(double amount);void withdraw(double amount);double getBalance();
}
Step 2: 实现具体的银行账户类(与静态代理相同)
// 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() 方法拦截对目标对象的方法调用。

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: 动态代理测试
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 依赖

<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 可以代理这个类。

// 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() 方法来拦截目标类的方法调用

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

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.rhkb.cn/news/426223.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

6个Python小游戏项目源码【免费】

6个Python小游戏项目源码 源码下载地址&#xff1a; 6个Python小游戏项目源码 提取码: bfh3

Python异常处理:自定义异常②

文章目录 1. 什么是自定义异常&#xff1f;2. 为什么需要自定义异常&#xff1f;3. 如何定义自定义异常&#xff1f;3.1 基本自定义异常3.2 带详细信息的自定义异常3.3 自定义异常的继承层次 4. 使用自定义异常4.1 抛出自定义异常4.2 捕获自定义异常 5. 自定义异常的应用场景5.…

【C/C++】涉及string类的经典OJ编程题

【C/C】涉及string类的经典OJ编程题 一. 把字符串转化成整数&#xff08;atoi&#xff09;解法一&#xff1a;&#xff08;不用long&#xff09;完整代码&#xff1a;解法二&#xff1a;&#xff08;用long&#xff09; 二.字符串相加代码实现&#xff08;含注释&#xff09;&a…

【UE5】使用2DFlipbook图作为体积纹理,实现实时绘制体积纹理【第一篇】

这是一篇对“Creating a Volumetric Ray Marcher-Shader Bits”的学习心得 文章时间很早&#xff0c;因此这里针对UE5对原文做出兼容性修正&#xff08;为避免累赘不做出注明。链接如上&#xff0c;有需要自行学习&#xff09; 以及最后对Custom做可能的蓝图移植&#xff0c;做…

【Android Studio】2024.1.1最新版本AS调试老项目(老版AS项目文件、旧gradle)导入其他人的项目

文章目录 实验环境开始修改项目文件1. 删除.gradle及.idea两个文件夹2.修改SDK路径&#xff08;本地SDK存放路径&#xff09;3.修改gradle版本4.修改gradle插件版本&#xff08;AGP&#xff09;5.修改JDK版本 实验环境 Android Studio 版本 项目版本 开始修改项目文件 1. 删…

docker可视化管理工具推荐!docker.ui

正式介绍之前&#xff0c;可以看下这款工具的截图&#xff0c;开源地址在文末提供&#xff1a; docker.ui&#xff1a;一个可视化的docker管理工具 docker是一个开源的容器平台&#xff0c;可以让开发者和运维人员快速地构建、运行和部署应用。 docker的优势在于它可以实现应…

机器人的动力学——牛顿欧拉,拉格朗日,凯恩

机器人的动力学推导方法有很多&#xff0c;常用得有牛顿&#xff0c;拉格朗日&#xff0c;凯恩等方法&#xff0c;接下来&#xff0c;简单说说他们之间的使用。注&#xff1a;这里不考虑怎么来的&#xff0c;只说怎么应用。 参考1&#xff1a;4-14动力学分析方法-牛顿—欧拉方…

网络设备登录——《路由与交换技术》实验报告

目录 一、实验目的 二、实验设备和环境 三、实验记录 1.通过 Console 登录 步骤1:连接配置电缆。 步骤2:启动PC,运行超级终端。 步骤3:进入Console 配置界面 2.通过 Telnet 登录 步骤1:通过 Console 接口配置 Telnet 用户。 步骤2:配置 super 口令 步骤3:配置登录欢迎…

【数据仓库】数据仓库常见的数据模型——维度模型

文章部分图参考自&#xff1a;多维数据模型各种类型&#xff08;星型、雪花、星座、交叉连接&#xff09; - 知乎 (zhihu.com) 文章部分文字canla一篇文章搞懂数据仓库&#xff1a;四种常见数据模型&#xff08;维度模型、范式模型等&#xff09;-腾讯云开发者社区-腾讯云 (ten…

Comsol 利用多孔材料填充复合吸声器,拓宽低频完美吸声

参考文献&#xff1a;Cheng B , Gao N , Huang Y ,et al.Broadening perfect sound absorption by composite absorber filled with porous material at low frequency:[J].Journal of Vibration and Control, 2022, 28(3-4):410-424.DOI:10.1177/1077546320980214. 为了提高低…

MySQL基于GTID同步模式搭建主从复制

系列文章目录 rpmbuild构建mysql5.7.42版本的rpm包 文章目录 系列文章目录一、mysql-5.7.42RPM包构建二、同步模式分类介绍1.异步同步模式2.半同步模式2.1.实现半同步操作流程2.2.半同步问题总结2.3.半同步一致性2.4.异步与半同步对比 3.GTID同步 三、GTID同步介绍1.gtid介绍2…

C语言程序设计(进阶)

行到水穷处&#xff0c;坐看云起时。 中秋快乐呀&#xff01; 数据在内存中的存储 1.数据类型的介绍 &#xff08;1&#xff09;基本的内置类型&#xff1a; char //字符数据类型 short //短整型 int //整型 long //长整型 …

【零基础速领】全套AI大模型入门指南(学习路线+PDF文档+面试)

已经有越来越多的人开始认识到学习AI的重要性了&#xff01;可能是自主的认知&#xff0c;也可能是被身边的人卷的。总之&#xff0c;可能已经没有人不知道人工智能这个概念了&#xff0c;可能人人都已知道ChatGPT了&#xff0c;哪怕他没有用过。 ChatGPT发布后&#xff0c;很…

nginx实现https安全访问的详细配置过程

文章目录 前言什么是 HTTP&#xff1f;什么是 HTTPS&#xff1f;HTTP 和 HTTPS 的区别为什么 HTTPS 被称为安全的&#xff1f;配置过程配置自签名证书 前言 首先我们来简单了解一下什么是http和https以及他们的区别所在. 什么是 HTTP&#xff1f; HTTP&#xff0c;全称为“超…

LeetCode_sql_day24(1212.查询球队积分)

描述 表: Teams ------------------------- | Column Name | Type | ------------------------- | team_id | int | | team_name | varchar | ------------------------- team_id 是该表具有唯一值的列。 表中的每一行都代表一支独立足球队。表: Matches…

【Linux】探索文件I/O奥秘,解锁软硬链接与生成动静态库知识

目录 1、C文件接口 1.1什么是当前路径&#xff1f; 1.2程序默认打开的文件流&#xff1a; 2、系统文件I/O 2.1.接口介绍&#xff1a; 2.1.1open&#xff1a; 参数讲解; flags如何实现一个参数就可以有多个参数传参的效果&#xff1f; open函数的返回值&#xff1a; 3…

CentOS入门必备基础知识

CentOS&#xff08;Community ENTerprise Operating System&#xff09;是基于红帽企业版Linux&#xff08;RHEL&#xff09;的免费开源Linux发行版&#xff0c;它以稳定、安全和可靠性著称&#xff0c;被广泛应用于服务器环境。以下是CentOS入门时你必须掌握的基础知识。 1. C…

【技术调研】三维(3)-ThreeJs-几何体、材质、贴图、灯光及案例

几何体 ​ 几何体是构建模型的基础,模型=几何体+材质。threejs中已内置了很多几何体。这里不一一介绍。 BufferGeometry 是面片、线或点几何体的有效表述。包括顶点位置,面片索引、法相量、颜色值、UV 坐标和自定义缓存属性值。使用 BufferGeometry 可以有效减少向 GPU 传输…

报名开启!第七届“强网”拟态防御国际精英挑战赛正式官宣

向新向未来&#xff0c;顶赛启新篇&#xff01;第七届“强网”拟态防御国际精英挑战赛正式官宣&#xff0c;暂定于2024年11月18日至21日在南京举办。 本届大赛旨在促进内生安全理念和技术在实践中的应用&#xff0c;吸引更多数字化产业加入内生安全产业生态圈&#xff0c;推动…

基于C++实现(MFC)职工工作量统计系统

题目&#xff1a;职工工作量统计系统设计 1、问题描述 职工包括姓名、职工号、性别、年龄、所在部门、联系方式等信息。 工作量包括职工号、完成的产品数量等信息。 该设计系统能够对职工的工作量进行统计&#xff0c;并排出名次。注意&#xff0c;一个职工的工作量是可以多次…