深入解析代理模式:静态代理、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.mzph.cn/pingmian/54026.shtml

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

相关文章

Cortex-A7的GIC(通用中断控制器):边沿触发和电平触发中断的区别

0 资料 ARM Generic Interrupt Controller Architecture version 2.0 Architecture Specification1 边沿触发和电平触发中断的区别 1.1 边沿触发和电平触发中断官方解释 边沿触发&#xff08;Edge-triggered&#xff09; This is an interrupt that is asserted on detectio…

DFS:深搜+回溯+剪枝实战解决OJ问题

✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来! 文章目录 目录 文章目录 前言 一 排列、子集问题 1.1 全排列I 1.2 子集I 1.3 找出所有子集的异或总和 1.4 全排列II 1.5 字母大小写全排列 1.6 优美的排列 二 组合问题 2.1 电话号码的数字组合 …

物联网架构

1 三层架构 三层架构就像我们拿着一个设备&#xff0c;通过网络直接连接到服务器获取结果&#xff0c;步骤简单。 举个例子&#xff1a;智能家居的温度监控系统 1. 感知层&#xff08;设备与传感器&#xff09; 在智能家居系统中&#xff0c;温度传感器被安装在家里的各个房间…

战斗机检测系统源码分享

战斗机检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Visio…

4.提升客户服务体验:ChatGPT在客服中的应用(4/10)

本文大纲旨在指导撰写一篇全面探讨ChatGPT如何通过优化客户服务流程、提供实际应用案例和用户反馈&#xff0c;以提升客户服务体验的深入博客文章。 引言 在当今竞争激烈的商业环境中&#xff0c;客户服务已成为企业成功的关键因素。优质的客户服务不仅能够增强客户满意度和忠…

第十一章 【后端】商品分类管理微服务(11.1)——创建父工程

第十一章 【后端】商品分类管理微服务 11.1 创建父工程 项目名称:EasyTradeManagerSystem:Easy 表示简单易用,Trade 表示交易,Manager 表示管理,System 表示系统,强调系统在商品交易管理方面的便捷性,简称 etms。 新建工程 yumi-etms yumi-etms 作为所有模块的父工程,…

1.使用 IDEA 过程中的英语积累 - File 菜单(每一次重点积累 5 个单词)

前言 学习可以不局限于传统的书籍和课堂&#xff0c;各种生活的元素也都可以做为我们的学习对象&#xff0c;本文将利用 IDEA 页面上的各种英文元素来做英语的积累&#xff0c;如此做有 3 大利 这些软件在我们工作中是时时刻刻接触的&#xff0c;借此做英语积累再合适不过&…

QT + WebAssembly + Vue环境搭建

Qt6.7.2安装工具 emsdk安装 git clone https://github.com/emscripten-core/emsdk.git cd emsdk emsdk install 3.1.50 emsdk activate 3.1.50 Qt Creator配置emsdk 效果 参考 GitHub - BrockReece/vue-wasm: Vue web assembly loader Emscripten cmake多版本编译-CSDN博客 …

vue使用TreeSelect设置带所有父级节点的回显

Element Plus的el-tree-select组件 思路&#xff1a; 选中节点时&#xff0c;给选中的节点赋值 pathLabel&#xff0c;pathLabel 为函数生成的节点名字拼接&#xff0c;数据源中不包含。 在el-tree-select组件中设置 props“{ label: ‘pathLabel’ }” 控制选中时input框中回…

如何使用ssm实现企业人事管理系统+vue

TOC ssm628企业人事管理系统vue 研究背景 自计算机发展以来给人们的生活带来了改变。第一代计算机为1946年美国设计&#xff0c;最开始用于复杂的科学计算&#xff0c;占地面积、开机时间要求都非常高&#xff0c;经过数十几的改变计算机技术才发展到今天。现如今已是电子时…

Holynix: v1

确认物理地址 00:0C:29:BC:05:DE ip扫描 arp-scan -l 端口扫描 nmap 192.168.48.167 访问一下80端口 burp抓包 找到一个登录框 想着burp抓包试试 将抓到的包放入kali中的文件中使用sqlmap注入试试 sqlmap 存在sql注入 sqlmap -r password --batch --random-agent 发现…

卷积神经网络经典模型架构简介

【图书推荐】《PyTorch深度学习与企业级项目实战》-CSDN博客 《PyTorch深度学习与企业级项目实战&#xff08;人工智能技术丛书&#xff09;》(宋立桓&#xff0c;宋立林)【摘要 书评 试读】- 京东图书 (jd.com) ImageNet是一个包含超过1 500万幅手工标记的高分辨率图像的数据…

CAS 和 synchronized 的优化过程

&#x1f349; 目录 CAS 的实现 CAS 的工作原理 优化过程 CAS 的应用 1) 实现原子类 2&#xff09;实现自旋锁 CAS 的 ABA 问题 synchronized 的 原理 synchronized 基本特点 加锁工作过程 其他优化操作 1. 锁消除 2. 锁粗化 CAS&#xff08;Compare-And-Swap&…

2024ICPC网络赛第一场

A 最终答案与中国队能力值的排名有关&#xff0c;具体每个情况手推一下&#xff0c;用 if else 即可通过。 #include <bits/stdc.h> using namespace std;int main() {ios::sync_with_stdio(false); cin.tie(0);int t, a[40];cin >> t;while (t--) {int num 0;f…

Arduino IDE离线配置第三方库文件-ESP32开发板

简洁版可以使用uget等&#xff0c;将文件下载到对应文件夹下&#xff0c;然后安装。 esp32之arduino配置下载提速 录屏 Arduino IDE离线配置第三方库文件ESP32 资源 Linux https://download.csdn.net/download/ZhangRelay/89749063 第三方开发板 非默认支持的开发板 linu…

Ubuntu24.04部署docker

1、更新软件 apt update 2、安装curl apt install apt-transport-https curl 3、导入阿里云GPG秘钥 curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg 4、添加Docker阿里云仓库到Ubuntu 24.04的…

Python编码系列—Python适配器模式:无缝集成的桥梁

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

LLM - 理解 多模态大语言模型 (MLLM) 的指令微调与相关技术 (四)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/142063880 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 完备(F…

如何看待IBM中国研发部裁员

如何看待IBM中国研发部裁员&#xff1f; 近日&#xff0c;IBM中国宣布撤出在华两大研发中心&#xff0c;引发了IT行业对于跨国公司在华研发战略的广泛讨论。这一决定不仅影响了众多IT从业者的职业发展&#xff0c;也让人思考全球化背景下中国IT产业的竞争力和未来发展方向。面对…

文件格式转换:EXCEL和CSV文件格式互相转换

目录 1.EXCEl和CSV文件格式互相转换1.1首先安装所需的Python包1.2excel转换为csv代码如下&#xff1a;1.3csv转换为excel代码如下&#xff1a; 由于excel文件在数学建模数据处理当中的局限性&#xff0c;我们通常把excel文件转换为csv文件来处理&#xff0c;下面是相关的代码&a…