代理模式:掌控对象访问的优雅之道

引言:设计模式的重要性

在软件开发中,设计模式是解决常见问题的可复用方案,它们如同建筑师的蓝图,为开发者提供了经过验证的最佳实践。在23种经典设计模式中,代理模式因其独特的访问控制能力增强功能特性而脱颖而出。代理模式不仅广泛应用于日常开发,更是构建高性能、可维护系统的关键组件。无论是实现延迟加载、访问控制,还是简化复杂系统结构,代理模式都发挥着不可替代的作用。

什么是代理模式?

定义与核心思想

代理模式(Proxy Pattern)是一种结构型设计模式,它提供了一个代理对象,用来控制对其他对象的访问。代理模式的核心思想是:在不改变原始类的情况下,通过引入代理类来增强功能

通俗地说,代理模式就像明星的经纪人:当你想联系明星时,你需要先通过经纪人。经纪人可以决定是否安排见面、过滤不合理的请求,甚至在必要时代为处理某些事务。

模式结构

代理模式包含三个核心角色:

  1. 抽象主题(Subject):定义真实主题和代理主题的公共接口
  2. 真实主题(Real Subject):实现具体业务逻辑的实际对象
  3. 代理(Proxy):持有对真实主题的引用,控制对真实主题的访问
持有引用
«interface»
Subject
+request()
RealSubject
+request()
Proxy
-realSubject: RealSubject
+request()

代理模式的分类

1. 静态代理

在编译期就确定代理关系的模式,需要手动创建代理类。

特点

  • 实现简单直观
  • 需要为每个被代理类创建代理类
  • 代码冗余度高

2. 动态代理

在运行时动态生成代理类的模式,无需手动编写代理类。

细分类型

  • JDK动态代理:基于接口实现,使用Java反射机制
  • CGLIB动态代理:基于字节码操作,可代理普通类

3. 按功能分类

  • 远程代理:为不同地址空间的对象提供本地代表
  • 虚拟代理:延迟创建开销大的对象
  • 保护代理:控制对原始对象的访问权限
  • 缓存代理:为开销大的运算结果提供缓存
  • 智能引用代理:在对象被引用时执行额外操作

静态代理实现详解

场景:文件加载系统

考虑一个文件加载系统,我们需要在加载文件前进行权限验证,加载后记录日志。

类结构

// 抽象主题:文件加载接口
public interface FileLoader {void loadFile(String filename);
}// 真实主题:实际文件加载器
public class RealFileLoader implements FileLoader {@Overridepublic void loadFile(String filename) {System.out.println("正在加载文件: " + filename);// 实际的文件加载操作...}
}// 代理类:增强的文件加载器
public class FileLoaderProxy implements FileLoader {private RealFileLoader realFileLoader;public FileLoaderProxy() {this.realFileLoader = new RealFileLoader();}@Overridepublic void loadFile(String filename) {// 前置增强:权限验证if (!checkAccess()) {System.out.println("访问被拒绝!");return;}// 调用真实对象的方法realFileLoader.loadFile(filename);// 后置增强:日志记录logAccess(filename);}private boolean checkAccess() {System.out.println("正在验证权限...");// 实际权限验证逻辑return true; // 简化为始终返回true}private void logAccess(String filename) {System.out.println("文件访问已记录: " + filename);}
}

客户端使用

public class Client {public static void main(String[] args) {FileLoader loader = new FileLoaderProxy();loader.loadFile("重要文档.pdf");}
}

输出

正在验证权限...
正在加载文件: 重要文档.pdf
文件访问已记录: 重要文档.pdf

静态代理的优缺点

优点

  • 职责清晰,符合单一职责原则
  • 在不修改目标对象的前提下扩展功能
  • 开闭原则的良好实践

缺点

  • 代理类和目标类需实现相同接口
  • 每个服务类都需要创建代理类,导致类数量增加
  • 接口变更时,目标类和代理类都需要修改

动态代理深入解析

JDK动态代理

基于Java反射机制,在运行时动态创建代理类。

实现步骤

  1. 定义InvocationHandler接口实现类
  2. 使用Proxy.newProxyInstance()创建代理对象
  3. 通过代理对象调用方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 调用处理器
public class LoggingHandler implements InvocationHandler {private Object target;public LoggingHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 前置处理System.out.println("【日志】开始执行方法: " + method.getName());// 调用真实对象的方法Object result = method.invoke(target, args);// 后置处理System.out.println("【日志】方法执行完成: " + method.getName());return result;}// 创建代理对象public static Object createProxy(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new LoggingHandler(target));}
}

客户端使用

public class Client {public static void main(String[] args) {FileLoader realLoader = new RealFileLoader();FileLoader proxy = (FileLoader) LoggingHandler.createProxy(realLoader);proxy.loadFile("年度报告.pdf");}
}

输出

【日志】开始执行方法: loadFile
正在加载文件: 年度报告.pdf
【日志】方法执行完成: loadFile

CGLIB动态代理

适用于没有实现接口的类,通过操作字节码生成子类代理。

添加Maven依赖

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

实现代码

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;public class CglibProxyFactory implements MethodInterceptor {private Object target;public CglibProxyFactory(Object target) {this.target = target;}// 创建代理对象public Object getProxyInstance() {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(this);return enhancer.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("【CGLIB代理】方法调用前: " + method.getName());Object result = method.invoke(target, args);System.out.println("【CGLIB代理】方法调用后: " + method.getName());return result;}
}

JDK代理 vs CGLIB代理

特性JDK动态代理CGLIB代理
实现基础Java反射机制字节码操作
代理对象要求必须实现接口可代理普通类
性能调用方法较慢创建代理慢,执行快
依赖Java标准库需要第三方库
方法拦截通过InvocationHandler通过MethodInterceptor
生成方式运行时生成接口实现类运行时生成目标类的子类

代理模式的应用场景

  1. 访问控制:控制对敏感对象的访问权限
  2. 延迟初始化:推迟创建开销大的对象
  3. 本地代理:代表远程对象(如RPC调用)
  4. 日志记录:方法调用前后自动记录日志
  5. 缓存代理:缓存结果避免重复计算
  6. 智能引用:对象引用计数,自动释放资源
  7. AOP实现:切面编程的基础

代理模式的优缺点

优点

  • 职责清晰:真实对象只需关注核心逻辑
  • 高扩展性:无需修改目标对象即可增强功能
  • 访问控制:保护真实对象不被非法访问
  • 性能优化:通过延迟加载提高系统响应速度
  • 解耦合:客户端与真实对象解耦

缺点

  • 系统复杂度增加:引入了额外的代理层
  • 性能开销:代理调用增加处理时间(特别是动态代理)
  • 实现复杂度:动态代理使用较复杂
  • 代码可读性:可能降低代码的直观性

与其他模式的关系

代理 vs 装饰器

  • 相似点:都通过组合增强对象功能
  • 区别
    • 代理控制访问,装饰器增加行为
    • 代理通常预先确定关系,装饰器可动态组合
    • 代理通常管理生命周期,装饰器只增加功能

代理 vs 适配器

  • 适配器解决接口不兼容问题
  • 代理保持接口一致,控制访问

代理 vs 门面

  • 门面简化复杂系统的接口
  • 代理控制对单个对象的访问

实际应用案例

Spring框架中的代理

Spring AOP的核心就是代理模式:

  • JDK代理:用于接口实现的Bean
  • CGLIB代理:用于普通类
  • 通过@Transactional@Cacheable等注解实现声明式事务和缓存

MyBatis中的代理

  • Mapper接口通过MapperProxy实现动态代理
  • 将接口方法调用转换为SQL执行

RPC框架

  • 远程服务调用通过本地代理对象实现
  • 客户端像调用本地方法一样调用远程服务

总结

代理模式是对象访问控制的优雅解决方案,它通过引入代理层在不修改原始对象的前提下实现了功能的增强和访问的控制。无论是静态代理的直观简单,还是动态代理的灵活强大,代理模式都为我们提供了处理复杂场景的有效工具。

在现代框架中,代理模式无处不在:Spring AOP的切面编程、MyBatis的Mapper代理、RPC的远程调用等都深度依赖代理模式。掌握代理模式不仅能帮助我们理解这些框架的原理,更能提升我们设计高质量系统的能力。

选择代理类型时需权衡需求:简单场景可选静态代理;需要灵活扩展时,JDK代理适合接口实现,CGLIB代理适合普通类;特定功能场景可使用远程、虚拟等专用代理。

作为开发者,我们应当理解代理模式的本质——控制与增强,而非盲目套用。恰当地使用代理模式,可以让我们的系统更加灵活、安全和高效。

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

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

相关文章

sqli-labs靶场通关笔记:第18-19关 HTTP头部注入

第18关 User-Agent注入登录正确的用户名密码&#xff0c;它会将User-Agent的信息回显到页面上。猜测UA头可能存在注入点。利用bp抓包&#xff0c;在UA头后面加一个单引号&#xff0c;发现报错了。观察报错信息&#xff0c;显示nearxx,admin)&#xff0c;推测后面应该还有两个参…

基于按键开源MultiButton框架深入理解代码框架(三)(指针的深入理解与应用)

文章目录3、分析代码3.3 按键的插入3.4 按键的删除3.5 继续分析状态机核心理解4、写在最后的总结5、思想感悟篇6、慈悲不渡自绝人3、分析代码 3.3 按键的插入 // Button handle list headstatic Button* head_handle NULL;/*** brief Start the button work, add the handle…

ACOUSLIC-AI挑战报告:基于低收入国家盲扫超声数据的胎儿腹围测量|文献速递-医学影像算法文献分享

Title题目ACOUSLIC-AI challenge report: Fetal abdominal circumferencemeasurement on blind-sweep ultrasound data from low-income countriesACOUSLIC-AI挑战报告&#xff1a;基于低收入国家盲扫超声数据的胎儿腹围测量01文献速递介绍胎儿生长受限&#xff08;FGR&#xf…

集群聊天服务器各个类进行详解

1.dh.h类定义概要类名&#xff1a; MySQL功能&#xff1a; 简化MySQL的连接、查询和更新操作&#xff0c;提供接口给上层应用使用。成员变量private:MYSQL *_conn;_conn&#xff1a;指向MYSQL结构体的指针&#xff0c;用于代表数据库连接实例。由mysql_init()初始化&#xff0c…

电缆安全双保险:不止防盗,更能防触电的塔能智慧照明守护方案

城市照明、地下车库以及园区路灯所涉及的电缆安全问题&#xff0c;向来都是运维管理方面颇为棘手的难题。在传统的运维管理模式之下&#xff0c;电缆一旦被盗&#xff0c;那么所造成的影响可不小&#xff0c;一方面会带来直接的经济损失&#xff0c;另一方面还极有可能因为线路…

Leetcode刷题营第二十九,三十题:二叉树的中序以及后序遍历

94.二叉树的中序遍历 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&#x…

Rabbitmq Direct Exchange(直连交换机)可以保证消费不被重复消费吗,可以多个消费者,但是需要保证同一个消息,不会被投递给多个消费者

在 RabbitMQ 中&#xff0c;默认情况下&#xff0c;不能保证消息不被重复消费&#xff0c;但可以通过 队列绑定方式 消费者竞争机制 来确保 同一消息只被一个消费者处理。以下是几种可行的方案&#xff1a;方案 1&#xff1a;单队列 竞争消费者模式&#xff08;默认行为&…

常用的OTP语音芯片有哪些?

唯创知音在 OTP 语音芯片有着26年的历史&#xff0c;有着丰富的技术积累与产品迭代历程。1999 年&#xff0c;唯创知音在广州成立&#xff0c;彼时便开始在电子领域积极探索。2000 年&#xff0c;公司敏锐捕捉到语音芯片行业的发展潜力&#xff0c;正式进军该领域。经过数年技术…

分布式光伏发电系统中的“四可”指的是什么?

在分布式光伏电站规模爆发式增长的今天&#xff0c;“看不见、管不住、调不动”的难题却成为行业痛点。如何让散布各处的光伏电站真正成为稳定高效的“绿色能量站”&#xff1f;2025年《分布式光伏发电开发建设管理办法》大型工商业项目&#xff08;≥6MW&#xff09;明确要求具…

健康管理系统新趋势:AI + 物联网如何重塑健康管理

一、传统健康管理的痛点与变革之必然长久以来&#xff0c;我们熟悉的健康管理方式存在明显局限&#xff1a;数据孤岛严重&#xff1a;体检报告在抽屉里沉睡&#xff0c;健身手环数据仅存于手机&#xff0c;不同医疗机构信息互不相通&#xff0c;个人健康信息犹如碎片散落各处。…

git基本操作【GIT-2】

git基本操作初始化一个仓库&#xff08;repository&#xff09;、开始或停止跟踪&#xff08;track&#xff09;文件、暂存&#xff08;stage&#xff09;或提交&#xff08;commit&#xff09;更改如何配置 Git 来忽略指定的文件和文件模式、如何迅速而简单地撤销错误操作、如…

【数据准备】——深度学习.全连接神经网络

目录 1 数据加载器 1.1 构建数据类 1.1.1 Dataset类 1.1.2 TensorDataset类 1.2 数据加载器 2 数据加载案例 2.1 加载csv数据集 2.2 加载图片数据集 2.3 加载官方数据集 2.4 pytorch实现线性回归 1 数据加载器 分数据集和加载器2个步骤~ 1.1 构建数据类 1.1.1 Dat…

健康生活,从细节开始

健康生活&#xff0c;从细节开始在当今快节奏的生活中&#xff0c;健康逐渐成为人们关注的焦点。拥有健康的身体&#xff0c;才能更好地享受生活、追求梦想。那么&#xff0c;如何才能拥有健康呢&#xff1f;这就需要我们从生活中的点滴细节入手&#xff0c;培养良好的生活习惯…

javax.servlet.http.HttpServletResponse;API导入报错解决方案

javax.servlet.http.HttpServletResponse;API导入报错解决方案与Postman上传下载文件验证 1. 主要错误&#xff1a;缺少 Servlet API 依赖 错误信息显示 javax.servlet.http 包不存在。这是因为你的项目缺少 Servlet API 依赖。 解决方案&#xff1a; 如果你使用的是 Maven&…

reids依赖删除,但springboot仍然需要redis参数才能启动

背景&#xff1a;项目需要删除redis。我删除完项目所有配置redis的依赖&#xff0c;启动报错。[2025-07-17 15:08:37:561] [DEBUG] [restartedMain] DEBUG _.s.w.s.H.Mappings - [detectHandlerMethods,295] [] - o.s.b.a.w.s.e.BasicErrorController:{ [/error]}: error(HttpS…

【前端】CSS类命名规范指南

在 CSS 中&#xff0c;合理且规范的 class 命名格式对项目的可维护性和协作效率至关重要。以下是主流的 class 命名规范和方法论&#xff1a;一、核心命名原则语义化命名&#xff1a;描述功能而非样式 ✅ .search-form&#xff08;描述功能&#xff09;❌ .red-text&#xff08…

C++网络编程 4.UDP套接字(socket)编程示例程序

以下是基于UDP协议的完整客户端和服务器代码。UDP与TCP的核心区别在于无连接特性&#xff0c;因此代码结构会更简单&#xff08;无需监听和接受连接&#xff09;。 UDP服务器代码&#xff08;udp_server.cpp&#xff09; #include <iostream> #include <sys/socket.h&…

King’s LIMS:实验室数字化转型的智能高效之选

实验室数字化转型不仅是技术升级&#xff0c;更是管理理念和工作方式的革新。LIMS系统作为这一转型的核心工具&#xff0c;能够将分散的实验数据转化为可分析、可复用的资产&#xff0c;为科研决策提供支持&#xff1b;规范检测流程&#xff0c;减少人为干预&#xff0c;确保结…

【力扣 中等 C】97. 交错字符串

目录 题目 解法一 题目 待添加 解法一 bool isInterleave(char* s1, char* s2, char* s3) {const int len1 strlen(s1);const int len2 strlen(s2);const int len3 strlen(s3);if (len1 len2 ! len3) {return false;}if (len1 < len2) {return isInterleave(s2, s1,…

Class9简洁实现

Class9简洁实现 %matplotlib inline import torch from torch import nn from d2l import torch as d2l# 初始化训练样本、测试样本、样本特征维度和批量大小 n_train,n_test,num_inputs,batch_size 20,100,200,5 # 设置真实权重和偏置 true_w,true_b torch.ones((num_inputs…