【设计模式】【结构型模式】代理模式(Proxy)

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
🎵 当你的天空突然下了大雨,那是我在为你炸乌云

文章目录

  • 一、入门
    • 什么是代理模式?
    • 为什么要代理模式?
    • 如何实现代理模式?
      • 静态代理
      • JDK代理
      • CGLIB动态代理
      • 三种代理模式对比
  • 二、代理模式在框架源码中的运用
    • Spring Framework 中的 AOP 代理
    • MyBatis 中的 Mapper 接口代理
  • 三、总结
    • 代理模式的优点
    • 代理模式的缺点
    • 代理模式的适用场景

一、入门

什么是代理模式?

代理模式(Proxy Pattern)是一种结构型设计模式,允许你提供一个代理对象来控制对另一个对象的访问。
代理对象在客户端和目标对象之间起到中介作用,可以在不改变目标对象的情况下增加额外的功能或控制访问。

为什么要代理模式?

  1. 违反单一职责原则
    • 原始对象的核心职责是实现业务逻辑,但如果将额外的功能(如权限检查、日志记录等)直接写入原始对象,会导致对象的职责变得复杂,难以维护。
    • 例如,一个UserService类如果既要处理用户登录逻辑,又要记录日志、检查权限,代码会变得臃肿。
  2. 代码重复
    • 如果多个地方需要对对象的访问进行相同的控制(如权限检查),开发者可能会在每个调用点重复编写相同的代码,导致代码冗余。
  3. 难以扩展
    • 如果需要在访问对象时增加新的功能(如缓存、延迟加载等),可能需要直接修改原始对象的代码,这会破坏开闭原则(对扩展开放,对修改关闭)。
  4. 性能问题
    • 某些场景下,对象的创建或初始化成本较高(如加载大文件、连接远程服务等)。如果没有代理模式,可能会在不需要时提前创建对象,导致资源浪费。
  5. 安全性问题
    • 如果没有代理模式,客户端可以直接访问敏感对象,可能会绕过必要的安全检查或权限控制。

如何实现代理模式?

  1. Subject(抽象主题):定义真实对象和代理对象的共同接口,客户端通过该接口与真实对象交互。
  2. RealSubject(真实主题):实现Subject接口,是代理对象所代表的真实对象。
  3. Proxy(代理):实现Subject接口,持有对RealSubject的引用,控制对RealSubject的访问,并可以在访问前后添加额外操作。

【案例】订单加强
网购订单处理:假设我们需要在用户下单时记录日志,但不想修改核心的订单处理逻辑。

静态代理

Subject(抽象主题): OrderService接口,定义订单下达。

public interface OrderService {void createOrder(String product);
}

RealSubject(真实主题)OrderServiceImpl类,实现下单逻辑。

public class OrderServiceImpl implements OrderService {@Overridepublic void createOrder(String product) {System.out.println("订单创建成功,商品:" + product);}
}

Proxy(代理)OrderServiceStaticProxy(手动编写代理类,添加日志记录)

public class OrderServiceStaticProxy implements OrderService {private OrderService orderService;public OrderServiceStaticProxy(OrderService orderService) {this.orderService = orderService;}@Overridepublic void createOrder(String product) {System.out.println("[静态代理] 记录日志:开始下单");orderService.createOrder(product);System.out.println("[静态代理] 记录日志:下单完成");}
}

测试类

public class Client {public static void main(String[] args) {OrderService realService = new OrderServiceImpl();OrderService proxy = new OrderServiceStaticProxy(realService);proxy.createOrder("iPhone 15");}
}

输出

[静态代理] 记录日志:开始下单
订单创建成功,商品:iPhone 15
[静态代理] 记录日志:下单完成

JDK代理

通过JDK的InvocationHandlerProxy类动态生成代理对象。
Subject(抽象主题): OrderService接口,定义订单下达。

public interface OrderService {void createOrder(String product);
}

RealSubject(真实主题)OrderServiceImpl类,实现下单逻辑。

public class OrderServiceImpl implements OrderService {@Overridepublic void createOrder(String product) {System.out.println("订单创建成功,商品:" + product);}
}

Proxy(代理)InvocationHandler类,InvocationHandler接口。

public class LogInvocationHandler implements InvocationHandler {private Object target; // 真实对象public LogInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("[JDK动态代理] 记录日志:开始执行方法 " + method.getName());Object result = method.invoke(target, args);System.out.println("[JDK动态代理] 记录日志:方法执行完成");return result;}
}

客户端调用

public class Client {public static void main(String[] args) {OrderService realService = new OrderServiceImpl();OrderService proxy = (OrderService) Proxy.newProxyInstance(realService.getClass().getClassLoader(),realService.getClass().getInterfaces(),new LogInvocationHandler(realService));proxy.createOrder("MacBook Pro");}
}

输出结果

[JDK动态代理] 记录日志:开始执行方法 createOrder
订单创建成功,商品:MacBook Pro
[JDK动态代理] 记录日志:方法执行完成

CGLIB动态代理

通过继承目标类生成子类来实现代理(无需接口)
RealSubject(真实主题)OrderServiceImpl类,实现下单逻辑。(这个增强无需接口)

// 真实对象(无需接口)
public class OrderService {public void createOrder(String product) {System.out.println("订单创建成功,商品:" + product);}
}

Proxy(代理)MethodInterceptor

public class LogMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("[CGLIB代理] 记录日志:开始执行方法 " + method.getName());Object result = proxy.invokeSuper(obj, args);System.out.println("[CGLIB代理] 记录日志:方法执行完成");return result;}
}

客户端

public class Client {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OrderService.class); // 设置父类enhancer.setCallback(new LogMethodInterceptor()); // 设置回调OrderService proxy = (OrderService) enhancer.create(); // 创建代理对象proxy.createOrder("AirPods Pro");}
}

输出结果

[CGLIB代理] 记录日志:开始执行方法 createOrder
订单创建成功,商品:AirPods Pro
[CGLIB代理] 记录日志:方法执行完成

三种代理模式对比

代理类型特点适用场景
静态代理手动编写代理类,直接调用目标对象。代理逻辑简单,目标对象固定。
JDK动态代理基于接口动态生成代理类,通过反射调用目标方法。需要代理接口的实现类。
CGLIB代理通过继承目标类生成子类代理,无需接口。性能较高,但生成代理类较慢。需要代理没有实现接口的类。

适用场景

  • 静态代理:适合代理逻辑简单且目标对象固定的场景。
  • JDK动态代理:适合基于接口的代理(如Spring AOP默认使用JDK代理)。
  • CGLIB代理:适合代理没有接口的类(如Spring AOP在类没有接口时自动切换为CGLIB)。

二、代理模式在框架源码中的运用

Spring Framework 中的 AOP 代理

Spring AOP 通过代理模式实现方法拦截(如事务管理、日志记录),核心类是 ProxyFactoryBean 和动态代理生成器。

核心角色与类:

  • Subject(抽象主题):被代理的接口或类(如 UserService 接口)。
  • RealSubject(真实主题):实际的目标对象(如 UserServiceImpl 类)。
  • Proxy(代理):由 Spring 动态生成的代理对象,具体分为两类:
  • JDK 动态代理:通过 JdkDynamicAopProxy 类生成(代理接口)。
  • CGLIB 代理:通过 ObjenesisCglibAopProxy 类生成(代理类,无接口时使用)。
// Spring AOP 生成代理的核心逻辑(ProxyFactoryBean)
public class ProxyFactoryBean {private Object target;          // RealSubject(真实对象)private Class<?>[] interfaces;  // Subject(接口)private Advice advice;          // 增强逻辑(如事务、日志)public Object getObject() {// 根据配置选择生成 JDK 或 CGLIB 代理return createAopProxy().getProxy();}
}

执行流程:

  1. 客户端调用代理对象的方法(如userService.save())。
  2. 代理对象拦截方法调用,执行增强逻辑(如开启事务)。
  3. 代理对象通过反射调用真实对象的方法(UserServiceImpl.save())。
  4. 返回结果前,执行后置增强逻辑(如提交事务)。

MyBatis 中的 Mapper 接口代理

MyBatis 通过代理模式将 Mapper 接口的方法调用转换为 SQL 执行,核心类是 MapperProxy

核心角色与类

  • Subject(抽象主题)Mapper 接口(如 UserMapper)。
  • RealSubject(真实主题):不存在真实实现类,由代理直接处理逻辑。
  • Proxy(代理)MapperProxy 类,实现 InvocationHandler 接口,动态代理 Mapper 接口。
// MyBatis 的 Mapper 代理生成逻辑(MapperProxyFactory)
public class MapperProxyFactory<T> {private final Class<T> mapperInterface;public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}
}// MapperProxy 实现 InvocationHandler
public class MapperProxy<T> implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 将方法调用转换为 SQL 执行(如执行 select * from user where id = ?)return execute(method, args);}
}

执行流程

  1. 客户端调用 UserMapper.findById(1)
  2. MapperProxy 拦截方法调用,解析方法名和参数。
  3. 根据方法名找到对应的 SQL 语句并执行。
  4. 返回数据库查询结果。

三、总结

代理模式的优点

  1. 职责清晰
    • 代理对象负责处理与核心业务无关的逻辑(如权限检查、日志记录、延迟加载等),而真实对象只需关注核心业务逻辑。
    • 符合单一职责原则。
  2. 增强功能
    • 在不修改真实对象的情况下,通过代理对象增强功能(如事务管理、缓存、延迟加载等)。
  3. 解耦
    • 代理模式将客户端与真实对象解耦,客户端只需与代理对象交互,无需直接访问真实对象。
  4. 安全性
    • 代理对象可以控制对真实对象的访问,增加权限检查等安全措施。
  5. 灵活性
    • 动态代理(如 JDK 动态代理、CGLIB 代理)可以在运行时动态生成代理对象,适应不同的需求。

代理模式的缺点

  1. 复杂性增加
    • 引入代理对象会增加系统的复杂性,尤其是动态代理的实现需要理解反射和字节码生成技术。
  2. 性能开销
    • 代理模式可能会引入额外的性能开销,尤其是在频繁调用时(如动态代理的反射调用)。
  3. 代码冗余
    • 静态代理需要为每个真实对象编写代理类,可能导致代码冗余。
  4. 调试困难
    • 动态代理生成的代理类在运行时才存在,调试时可能不如静态代理直观。

代理模式的适用场景

  1. 远程代理
    • 为位于不同地址空间的对象提供本地代表(如 RPC 框架中的远程服务调用)。
  2. 虚拟代理
    • 延迟创建开销较大的对象,直到真正需要时才创建(如图片懒加载、大文件加载)。
  3. 保护代理
    • 控制对敏感对象的访问,基于权限决定是否允许访问(如权限校验)。
  4. 智能引用代理
    • 在访问对象时执行额外操作(如引用计数、懒加载、缓存等)。
  5. AOP(面向切面编程)
    • 在方法调用前后增加通用逻辑(如日志记录、事务管理、性能监控等)。
  6. 延迟初始化
    • 在需要时才初始化对象,节省资源(如 Hibernate 的延迟加载)。
  7. 简化客户端调用
    • 客户端只需与代理对象交互,无需关心真实对象的复杂性(如 MyBatis 的 Mapper 代理)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/70171.shtml

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

相关文章

平板作为电脑拓展屏

有线串流&#xff08;速度更快&#xff09; spacedesk 打开usb对安卓的连接 用usb线直接连接电脑和平板 无线串流&#xff08;延迟高&#xff0c;不推荐&#xff09; todesk pc和手机端同时下载软件&#xff0c;连接后可以进行远程控制或扩展屏幕 spacedesk 连接到同一个…

[文末数据集]ML.NET库学习010:URL是否具有恶意性分类

文章目录 ML.NET库学习010:URL是否具有恶意性分类项目主要目的和原理项目概述主要功能和步骤总结数据集地址ML.NET库学习010:URL是否具有恶意性分类 项目主要目的和原理 项目主要目的: 本项目的目的是通过分析URL的特征,构建一个机器学习模型来判断给定的URL是否具有恶意…

Zotero PDF Translate插件配置百度翻译api

Zotero PDF Translate插件可以使用几种翻译api&#xff0c;虽然谷歌最好用&#xff0c;但是由于众所周知的原因&#xff0c;不稳定。而cnki有字数限制&#xff0c;有道有时也不行。其他的翻译需要申请密钥。本文以百度为例&#xff0c;进行申请 官方有申请教程&#xff1a; Zot…

具身智能在智能巡检机器人中的应用——以开关柜带电操作机器人为例

随着机器人技术和人工智能的迅速发展&#xff0c;具身智能在各行业的应用日益广泛&#xff0c;尤其是在电力行业中的智能巡检领域。传统的电力巡检和维护工作通常需要人工操作&#xff0c;存在着高温、高压、强电磁场等危险环境&#xff0c;且效率较低。开关柜带电操作机器人作…

网络工程师 (43)IP数据报

前言 IP数据报是互联网传输控制协议&#xff08;Internet Protocol&#xff0c;IP&#xff09;的数据报格式&#xff0c;由首部和数据两部分组成。 一、首部 IP数据报的首部是控制部分&#xff0c;包含了数据报传输和处理所需的各种信息。首部可以分为固定部分和可变部分。 固定…

【SpringBoot苍穹外卖】debugDay0 打开前端页面

在某一天学完后&#xff0c;电脑关机&#xff0c;再打开啥都忘了&#xff0c;记起来一点点&#xff0c;前端页面打不开&#xff0c;后端控制台一直循环出错。原来是下面这样哈哈。 查看端口是否被别的程序占用的操作步骤 winR输入cmd打开命令行 netstat -ano | findstr "8…

docker 运行 芋道微服务

jar包打包命令 mvn clean install package -Dmaven.test.skiptrue创建文件夹 docker-ai 文件夹下放入需要jar包的文件夹及 docker-compose.yml 文件 docker-compose.yml 内容&#xff1a;我这里的是ai服务&#xff0c;所以将原先的文件内容做了变更&#xff0c;你们需要用到什…

MySQL-事务隔离级别

事务有四大特性&#xff08;ACID&#xff09;&#xff1a;原子性&#xff0c;一致性&#xff0c;隔离性和持久性。隔离性一般在事务并发的时候需要保证事务的隔离性&#xff0c;事务并发会出现很多问题&#xff0c;包括脏写&#xff0c;脏读&#xff0c;不可重复读&#xff0c;…

【MediaTek】 T750 openwrt-23.05编 cannot find dependency libexpat for libmesode

MediaTek T750 T750 采用先进的 7nm 制程,高度集成 5G 调制解调器和四核 Arm CPU,提供较强的功能和配置,设备制造商得以打造精巧的高性能 CPE 产品,如固定无线接入(FWA)路由器和移动热点。 MediaTek T750 平台是一款综合的芯片组,集成了 5G SoC MT6890、12nm 制程…

五十天精通硬件设计第32天-S参数

系列文章传送门 50天精通硬件设计第一天-总体规划-CSDN博客 目录 1. S参数基础 2. S参数在信号完整性中的作用 3. 单端 vs. 差分S参数 4. S参数的关键特性 5. S参数的获取与使用 6. S参数分析中的常见问题 7. 实际案例:PCIe通道分析 8. 工具推荐 总结 信号完整性中…

pytest asyncio 支持插件 pytest-asyncio

pytest 是 Python 测试框架&#xff0c;但其不支持基于 asyncio 的异步程序&#xff08;例如&#xff0c;测试 FastAPI 异步代码&#xff09;&#xff0c;pytest-asyncio 是一个 pytest 插件&#xff0c;该插件赋予 pytest 可以测试使用 asyncio 库代码的能力。 https://github…

Transformer以及BERT阅读参考博文

Transformer以及BERT阅读参考博文 Transformer学习&#xff1a; 已有博主的讲解特别好了&#xff1a; 李沐&#xff1a;Transformer论文逐段精读【论文精读】_哔哩哔哩_bilibili知乎&#xff1a;Transformer模型详解&#xff08;图解最完整版&#xff09; - 知乎 个人杂想&…

分享一款AI绘画图片展示和分享的小程序

&#x1f3a8;奇绘图册 【开源】一款帮AI绘画爱好者维护绘图作品的小程序 查看Demo 反馈 github 文章目录 前言一、奇绘图册是什么&#xff1f;二、项目全景三、预览体验3.1 截图示例3.2 在线体验 四、功能介绍4.1 小程序4.2 服务端 五、安装部署5.1 快速开始~~5.2 手动部…

【R语言】回归分析与判别分析

一、线性回归分析 1、lm()函数 lm()函数是用于拟合线性模型&#xff08;Linear Models&#xff09;的主要函数。线性模型是一种统计方法&#xff0c;用于描述一个或多个自变量&#xff08;预测变量、解释变量&#xff09;与因变量&#xff08;响应变量&#xff09;之间的关系…

Visual Studio Code支持WSL,直接修改linux/ubuntu中的文件

步骤1 开始通过 WSL 使用 VS Code | Microsoft Learn 点击远程开发扩展包。 步骤2 Remote Development - Visual Studio Marketplace 点击install&#xff0c; 允许打开Visual Studio Code。 步骤3 共有4项&#xff0c;一齐安装。 步骤4 在WSL Linux(Ubuntu)中&#xf…

Unreal5从入门到精通之如何在 C++ 中创建 UserWidget

文章目录 前言UUserWidget 子类示例创建我们的 C++ 类的新蓝图子类更改现有蓝图的父类现在我们有了 C++ 基类,下一步做什么?蓝图还是 C++?结论前言 在之前的教程中,我展示了如何在编辑器中创建 UserWidget 蓝图, 在本教程中,我们将创建一个新的基于 C++ 的子类UUserWid…

利用雪花算法+Redis 自增 ID,生成订单号

在我们的项目中&#xff0c;我们需要定义一些全局唯一的 ID&#xff0c;比如订单号&#xff0c;支付单号等等。 这些ID有以下几个基本要求&#xff1a; 1、不能重复 2、不可被预测 3、能适应分库分表 为了生成一个这样一个全局的订单号&#xff0c;自定义了一个分布式 ID …

下载安装运行测试开源vision-language-action(VLA)模型OpenVLA

1. 安装 项目官网OpenVLA 首先按照官网提示的以下代码&#xff0c;执行创建环境->安装最小依赖->git克隆项目等 # Create and activate conda environment conda create -n openvla python3.10 -y conda activate openvla# Install PyTorch. Below is a sample comma…

Postman接口测试的cookie,token,session....鉴权

在接口测试过程中&#xff0c;常常需要进行Cookie、Token或Session等鉴权操作。Postman是一种流行的API开发环境&#xff0c;可以使用其自带的功能来进行这些鉴权操作。 下面是关于Postman接口测试中Cookie、Token和Session鉴权的详细介绍。 Cookie鉴权 在Postman中使用Cook…

深度学习机器学习:常用激活函数(activation function)详解

目录 Sigmoid Function ReLU&#xff08;Rectified Linear Unit&#xff09; LeakyReLU&#xff08;Leaky Rectified Linear Unit&#xff09; ClippedReLU&#xff08;Clipped Rectified Linear Unit&#xff09; PRelu&#xff08;Parametric ReLU&#xff09; Tanh&am…