代理模式核心概念
代理模式核心概念
代理模式是一种结构型设计模式,通过创建一个代理对象来控制对原始对象的访问。主要分为两类:
一、静态代理 (Static Proxy)
定义:在编译期确定代理关系的模式,代理类和目标类都需要实现相同的接口。
核心特点:
- 手动编码:需要为每个目标类编写对应的代理类
- 编译时绑定:代理关系在编译期确定
- 强类型:代理类直接实现目标接口
- 无反射:直接调用目标方法,性能较高
实现步骤:
// 1. 定义接口
interface Database {void query(String sql);
}// 2. 真实目标类
class MySQL implements Database {public void query(String sql) {System.out.println("执行MySQL查询: " + sql);}
}// 3. 静态代理类
class DatabaseProxy implements Database {private Database target;public DatabaseProxy(Database target) {this.target = target;}public void query(String sql) {// 前置增强System.out.println("[日志] 开始执行查询: " + sql);// 调用真实对象target.query(sql);// 后置增强System.out.println("[日志] 查询完成");}
}// 4. 使用代理
public class Main {public static void main(String[] args) {Database realDB = new MySQL();Database proxy = new DatabaseProxy(realDB);proxy.query("SELECT * FROM users");}
}
输出:
[日志] 开始执行查询: SELECT * FROM users
执行MySQL查询: SELECT * FROM users
[日志] 查询完成
优点:
- 代码直观,易于理解
- 编译期检查,类型安全
- 执行效率高(无反射开销)
缺点:
- 每个目标类都需要创建代理类
- 接口变更时代码需要同步修改
- 无法动态扩展功能
适用场景:
- 代理少量固定类
- 需要严格类型检查的场景
- 性能敏感的场景
二、动态代理 (Dynamic Proxy)
定义:在运行时动态创建代理对象的模式,无需提前编写代理类。
核心特点:
- 运行时生成:代理类在程序运行时动态创建
- 基于接口:JDK动态代理要求目标类必须实现接口
- 反射机制:通过反射调用目标方法
- 灵活扩展:一个代理类可代理多个目标类
1. JDK 动态代理(基于接口)
// 1. 定义接口(同上)
interface Database { ... }// 2. 真实目标类(同上)
class MySQL implements Database { ... }// 3. 实现InvocationHandler
class LoggingHandler implements InvocationHandler {private Object target;public LoggingHandler(Object target) {this.target = target;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("[日志] 开始执行: " + method.getName());Object result = method.invoke(target, args);System.out.println("[日志] 执行完成");return result;}
}// 4. 使用代理
public class Main {public static void main(String[] args) {Database realDB = new MySQL();Database proxy = (Database) Proxy.newProxyInstance(Database.class.getClassLoader(),new Class[]{Database.class},new LoggingHandler(realDB));proxy.query("SELECT * FROM orders");}
}
2. CGLIB 动态代理(基于继承)
// 1. 目标类(无需接口)
class PaymentService {public void pay(double amount) {System.out.println("支付金额: " + amount);}
}// 2. 方法拦截器
class PaymentInterceptor implements MethodInterceptor {public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("[安全校验] 开始支付");Object result = proxy.invokeSuper(obj, args);System.out.println("[通知] 支付成功");return result;}
}// 3. 使用代理
public class Main {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(PaymentService.class);enhancer.setCallback(new PaymentInterceptor());PaymentService proxy = (PaymentService) enhancer.create();proxy.pay(199.99);}
}
输出:
[安全校验] 开始支付
支付金额: 199.99
[通知] 支付成功
优点:
- 无需编写代理类
- 支持代理多个目标类
- 功能扩展灵活
- 适应接口变化
缺点:
- JDK代理要求目标类必须实现接口
- CGLIB不能代理final类/方法
- 反射调用有性能开销
- 调试相对复杂
适用场景:
- AOP实现(如Spring)
- 远程方法调用(RPC)
- 事务管理
- 权限控制
- 日志记录
三、关键对比
特性 | 静态代理 | 动态代理 |
---|---|---|
创建时机 | 编译期 | 运行时 |
实现方式 | 手动编码代理类 | 自动生成字节码 |
接口要求 | 需要实现相同接口 | JDK代理需要接口/CGLIB不需要 |
性能 | 高(直接调用) | 中(反射调用) |
扩展性 | 差(每类需单独代理) | 强(通用代理处理器) |
代码复杂度 | 高(重复代码多) | 低(集中处理) |
维护成本 | 高(接口变更需修改) | 低(自动适应) |
代理类数量 | 与目标类数量相同 | 运行时动态生成 |
四、Spring框架中的应用
-
AOP实现:
- JDK动态代理:代理接口实现类
- CGLIB:代理无接口的类
// Spring配置强制使用CGLIB @EnableAspectJAutoProxy(proxyTargetClass = true)
-
事务管理:
@Transactional // 基于动态代理实现 public void transfer(Account from, Account to, double amount) {// ... }
-
解决循环依赖:
- 通过三级缓存存储代理对象
- 提前暴露代理对象解决依赖
-
声明式服务:
@Cacheable
缓存代理@Async
异步方法代理@Retryable
重试代理
最佳实践:在Spring Boot 3.x中,默认优先使用CGLIB代理(通过设置
spring.aop.proxy-target-class=true
),因为它能代理任何类而不仅限于接口实现类。
AOP 中的代理机制详解
在 AOP(面向切面编程)中,代理是实现横切关注点(如日志、事务、安全等)的核心技术。Spring AOP 主要使用动态代理实现切面功能,下面详细解析其在 AOP 中的应用:
一、代理在 AOP 中的作用
-
解耦核心业务与横切逻辑
- 代理对象包裹原始对象(Target)
- 在方法执行前后插入增强逻辑(Advice)
// 代理执行流程 proxy.method() {beforeAdvice(); // 前置增强target.method(); // 原始方法afterAdvice(); // 后置增强 }
-
实现方式对比
代理类型 实现机制 在 AOP 中的应用场景 静态代理 手动编写代理类 简单场景,不常用 动态代理 运行时生成字节码 Spring AOP 默认实现
二、Spring AOP 的代理实现
1. JDK 动态代理(基于接口)
- 触发条件:目标类实现了至少一个接口
- 实现原理:
public class JdkDynamicProxy {public static Object createProxy(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), // 关键:获取所有接口(proxy, method, args) -> {System.out.println("[前置增强]");Object result = method.invoke(target, args);System.out.println("[后置增强]");return result;});} }
2. CGLIB 动态代理(基于继承)
- 触发条件:目标类未实现接口
- 实现原理:
public class CglibProxy {public static Object createProxy(Class<?> targetClass) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(targetClass); // 关键:设置父类enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {System.out.println("[事务开始]");Object result = proxy.invokeSuper(obj, args);System.out.println("[事务提交]");return result;});return enhancer.create();} }
三、Spring AOP 代理工作流程
典型场景:日志记录切面
@Aspect
@Component
public class LoggingAspect {// 切点定义@Pointcut("execution(* com.example.service.*.*(..))")public void serviceMethods() {}// 环绕通知(最强大的通知类型)@Around("serviceMethods()")public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {String methodName = joinPoint.getSignature().getName();// 前置增强System.out.println("[LOG] 进入方法: " + methodName);try {// 执行原始方法Object result = joinPoint.proceed();// 后置增强System.out.println("[LOG] 方法成功: " + methodName);return result;} catch (Exception e) {// 异常增强System.out.println("[LOG] 方法异常: " + methodName);throw e;}}
}
代理执行时序:
- 容器创建目标 Bean(如
UserService
) - 检测到需要 AOP 增强
- 根据目标类选择代理方式:
- 有接口 → JDK 代理
- 无接口 → CGLIB 代理
- 生成代理对象并注入到依赖方
- 方法调用时执行增强链
四、关键特性解析
1. 代理选择策略
- Spring Boot 2.x+:默认优先使用 CGLIB
# 显式配置使用 CGLIB spring.aop.proxy-target-class=true
- 传统 Spring:按目标类是否实现接口自动选择
2. 代理限制与解决方案
问题 | 解决方案 |
---|---|
自调用失效(this调用) | 使用 AopContext.currentProxy() |
final 方法无法增强 | 避免对 final 方法使用 AOP |
构造方法不拦截 | 改用初始化回调(@PostConstruct ) |
私有方法不拦截 | Spring AOP 只拦截 public 方法 |
3. 性能优化建议
- 减少切点匹配复杂度:精确限定切点范围
// 优化前(低效) @Pointcut("execution(* com.example..*.*(..))")// 优化后(高效) @Pointcut("execution(public * com.example.service.*Service.*(..))")
- 避免在切面中做重型操作:如数据库访问
- 合理使用缓存:对重复计算的结果进行缓存
五、静态代理在 AOP 中的应用
虽然 Spring AOP 主要使用动态代理,但理解静态代理有助于掌握 AOP 本质:
手动实现 AOP 效果(伪代码)
// 原始类
class UserService {public void saveUser(User user) {// 业务逻辑}
}// 静态代理增强
class UserServiceProxy extends UserService {private UserService target;public UserServiceProxy(UserService target) {this.target = target;}@Overridepublic void saveUser(User user) {// 前置增强log.info("开始保存用户");// 调用原始方法target.saveUser(user);// 后置增强log.info("用户保存成功");}
}
注意:实际 Spring AOP 不采用此方式,因为需要为每个类创建代理,无法应对复杂系统。
六、最佳实践
-
优先使用接口:方便 JDK 代理,避免 CGLIB 限制
// 推荐实现接口 public class OrderService implements IOrderService {...}
-
最小化切面范围:精确控制增强目标
// 精确到具体方法 @Pointcut("execution(* com.example.service.OrderService.createOrder(..))")
-
谨慎使用 @Around:确保调用
proceed()
@Around("pointcut()") public Object aroundAdvice(ProceedingJoinPoint pjp) {// 必须调用 pjp.proceed()return pjp.proceed(); }
-
代理类型检测:
if (AopUtils.isJdkDynamicProxy(bean)) {// JDK 代理处理 } else if (AopUtils.isCglibProxy(bean)) {// CGLIB 代理处理 }
通过合理利用代理机制,Spring AOP 实现了业务逻辑与横切关注的完美解耦,是构建可维护、可扩展系统的关键技术。
相关文章:
代理模式核心概念
代理模式核心概念 代理模式是一种结构型设计模式,通过创建一个代理对象来控制对原始对象的访问。主要分为两类: 一、静态代理 (Static Proxy) 定义:在编译期确定代理关系的模式,代理类和目标类都需要实现相同的接口。 核心特点…...
uni-app学习笔记十五-vue3页面生命周期(二)
onShow:用于监听页面显示,页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面; onHide:监听页面隐藏,当离开当前页面时触发。 示例代码: <template><view>姓名:{{nam…...

贪心算法实战篇2
文章目录 前言序列问题摆动序列单调递增的数字 贪心解决股票问题买卖股票的最佳时机II 两个维度权衡问题分发糖果根据身高重建队列 前言 今天继续带大家进行贪心算法的实战篇2,本章注意来解答一些运用贪心算法的中等的问题,大家好好体会,怎么…...

Java 大视界 -- Java 大数据机器学习模型在元宇宙虚拟场景智能交互中的关键技术(239)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
Flask中关于app.url_map属性的用法
目录 一、app.url_map 是什么? 二、可以查看哪些信息? 三、示例:打印所有路由 四、结合 url_for() 使用 五、常见用途场景 六、结合 Flask CLI 使用 总结 app.url_map 是 Flask 中非常重要的一个属性,用于查看或操作整个应用的 URL 路由映射表(routing map)。它展…...

高速串行接口
1.网口设计方案 上图中给出了两种网口设计方案,最上面是传统设计方式,下面是利用GT作为PHY层的设计,然后FPGA中设计协议层和MAC层。 2.SRIO SRIO的本地操作和远程操作 3.其他高速接口 srio rapid io aurora8b10b aurora64b66b pcie s…...

学习STC51单片机23(芯片为STC89C52RCRC)
每日一言 成功的路上从不拥挤,因为坚持的人不多,你要做那个例外。 通过单片机发指令给ESP8266进行通信 通信原理(也是接线原理) 代码如下 代码解释一下,因为我们的指令是字符数组(c语言没有字符串的概念),…...

一个完整的日志收集方案:Elasticsearch + Logstash + Kibana+Filebeat (一)
整体链路 [应用服务器] --> [Filebeat] --> [Logstash] --> [Elasticsearch] --> [Kibana] 组件职责 Kibana: 可视化和分析日志数据Elasticsearch: 存储和索引日志数据Logstash: 解析、转换和丰富日志数据Filebeat:…...

网络系统中安全漏洞扫描为何重要?扫描啥?咋扫描?
在网络系统中,安全漏洞扫描占据着极其重要的位置,这一环节有助于我们发现并消除潜在的安全隐患,进而提高网络安全防护的等级。下面,我将对此进行详尽的说明。 基本概念 漏洞扫描技术可以揭示并评估网站存在的安全风险࿰…...
HiveSQL语法全解析与实战指南
Hive SQL完整语法体系与特性解析 一、数据定义语言(DDL) 库操作 CREATE DATABASE [IF NOT EXISTS] dbname[COMMENT 描述][LOCATION hdfs_path][WITH DBPROPERTIES (keyvalue)];ALTER DATABASE dbname SET DBPROPERTIES (keyvalue); DROP DATABASE [IF…...
【conda报错】InvalidArchiveError
InvalidArchiveError - conda - Conda Community Forum 还是pip安装吧...

Socket 编程 TCP
目录 1. TCP socket API 详解 1.1 socket 1.2 bind 1.3 listen 1.4 accept 1.5 read&&write 1.6 connect 1.7 recv 1.8 send 1.9 popen 1.10 fgets 2. EchoServer 3. 多线程远程命令执行 4. 引入线程池版本翻译 5. 验证TCP - windows作为client访问Linu…...
Redis-6.2.9 Sentinel 哨兵配置
目录 1 操作系统信息和redis软件版本 2 集群架构图 3 部署redis主从 4 sentinel 配置文件 5 运维管理 6 go编写应用业务测试 哨兵核心功能:能够后台监控redis主机是否故障,如果故障了根据投票自动将从库转换为主库 1 操作系统信息和redis软件版本 rootu24-re…...

基于TMC5160堵转检测技术的夹紧力控制系统设计与实现
点击下面图片带您领略全新的嵌入式学习路线 🔥爆款热榜 90万阅读 1.6万收藏 一、技术背景与系统原理 在工业自动化领域,夹紧力控制是精密装配、机床夹具等场景的核心需求。传统方案多采用压力传感器伺服电机的闭环控制方式,但存在系统复杂…...
从零开始搞个简易分布式部署环境
从零开始,意味着连个服务器都没有,所以第一步,随便上哪个顺眼的云厂家去租个便宜大碗的服务器(不要window系统的就行),说大碗也不太对,主要是这碗能在手里用得久,这个就自己扒拉去了…...

XCTF-web-fileclude
解析如下 <?php include("flag.php"); // 包含敏感文件(通常包含CTF挑战的flag) highlight_file(__FILE__); // 高亮显示当前PHP文件源代码(方便查看代码逻辑)if(isset($_GET["file1"]…...

OpenShift AI - 启用过时版本的 Notebook 镜像
《OpenShift / RHEL / DevSecOps 汇总目录》 说明:本文已经在 OpenShift 4.18 OpenShift AI 2.19 的环境中验证 文章目录 查看可用 Notebook 镜像控制台查看命令行查看 Notebook 镜像、Image Stream 和 Image Registry Repository 对应关系启用老版本的 Notebook 镜…...

Redis 缓存穿透、缓存击穿、缓存雪崩详解与解决方案
在分布式系统中,Redis 凭借高性能和高并发处理能力,成为常用的缓存组件。然而,在实际应用中,缓存穿透、缓存击穿、缓存雪崩这三大问题会严重影响系统的性能与稳定性。本文将详细解析这三个问题的成因,并提供对应的解决…...
sass高阶应用
Sass(尤其是 SCSS 语法)除了基础功能外,还提供了许多高级特性,可以实现更灵活、可维护的样式系统。以下是 Sass 的 高级语法和应用技巧,适合中大型项目或组件库开发。 文章目录 一、控制指令(Control Directives)1. `@if / @else`2. `@for` 循环3. `@each` 遍历列表/Map…...
docker docker-ce docker.io
Ubuntu安装 更新软件包列表 首先确保软件包列表是最新的: sudo apt-get update 使用正确的卸载命令 替换 docker-engine 为 docker-ce 或 docker.io: sudo apt-get remove docker docker-ce docker.io containerd runc 检查已安装的 Do…...

DQN和DDQN(进阶版)
来源: *《第五章 深度强化学习 Q网络》.ppt --周炜星、谢文杰 一、前言 Q表格、Q网络与策略函数 Q表格是有限的离散的,而神经网络可以是无限的。 对于动作有限的智能体来说,使用Q网络获得当下状态的对于每个动作的 状态-动作值 。那么 a…...

【组件】翻牌器效果
目录 效果组件代码背景素材 效果 组件代码 <template><divclass"card-flop":style"{height: typeof height number ? ${height}px : height,--box-width: typeof boxWidth number ? ${boxWidth}px : boxWidth,--box-height: typeof boxHeight nu…...

CentOS 7 环境中部署 LNMP(Linux + Nginx + MySQL 5.7 + PHP)
在 CentOS 7 环境中部署 LNMP(Linux Nginx MySQL 5.7 PHP) 环境的详细步骤如下。此方案确保各组件版本兼容,并提供完整的配置验证流程。 1. 更新系统 sudo yum update -y 2. 安装 MySQL 5.7 2.1 添加 MySQL 官方 YUM 仓库 由于MySQL并不…...

NX811NX816美光颗粒固态NX840NX845
NX811NX816美光颗粒固态NX840NX845 美光NX系列固态硬盘颗粒深度解析:技术、性能与市场全景透视 一、技术架构与核心特性解析 1. NX811/NX816:入门级市场的平衡之选 技术定位:基于176层TLC(Triple-Level Cell)3D NAN…...

捋捋wireshark
本猿搬砖时会用到wireshark分析pcap包,但频率不高,记过一些笔记,今天捋捋,希望能给初学者节省一点时间。 wireshark是个网络封包分析软件(network packet analyzer),可以用来抓流量包ÿ…...

c++学习之---模版
目录 一、函数模板: 1、基本定义格式: 2、模版函数的优先匹配原则: 二、类模板: 1、基本定义格式: 2、类模版的优先匹配原则(有坑哦): 3、缺省值的设置: 4、ty…...
MyBatis-Flex 全面指南:下一代轻量级持久层框架实战入门
🚀 MyBatis-Flex 全面指南:下一代轻量级持久层框架实战入门 本文将带你全面了解 MyBatis-Flex 的特性、常见用法、最佳实践,帮助你高效构建更简洁、更灵活的 Java 持久层代码。 🧩 什么是 MyBatis-Flex? MyBatis-Flex…...

第十六章 EMQX黑名单与连接抖动检测
系列文章目录 第一章 总体概述 第二章 在实体机上安装ubuntu 第三章 Windows远程连接ubuntu 第四章 使用Docker安装和运行EMQX 第五章 Docker卸载EMQX 第六章 EMQX客户端MQTTX Desktop的安装与使用 第七章 EMQX客户端MQTTX CLI的安装与使用 第八章 Wireshark工具的安装与使用 …...
WebSphere(WAS)
WebSphere (WebSphere Application Server)为 SOA 环境提供软件,以实现动态的、互联的业务流程,为所有业务情形提供高度有效的应用程序基础架构。WebSphere 是 IBM 的应用程序和集成软件平台,包含所有必要的中间件基础…...
新编辑器编写指南--给自己的备忘
欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。 新的改变 我们对Markdown编辑器进行了一些功能拓展与语法支持&#x…...