当前位置: 首页 > news >正文

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

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

引言:设计模式的重要性

在软件开发中,设计模式是解决常见问题的可复用方案,它们如同建筑师的蓝图,为开发者提供了经过验证的最佳实践。在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.dtcms.com/a/279415.html

相关文章:

  • CVE-2017-7525源码分析与漏洞复现(Jackson 反序列化)
  • Android 中 实现格式化字符串
  • vue2/3生命周期使用建议
  • TCL在芯片设计与验证中的应用实践
  • WinUI3开发_Combobox实现未展开时是图标下拉菜单带图标+文字
  • ConcurrentHashMap 原子操作详解:computeIfAbsent、computeIfPresent和putIfAbsent
  • 技术人生——第12集:思想为王,功能在后
  • (5)LangGraph4j框架ReActAgent实现
  • mit6.5840-lab4C-Snapshot-25Summer
  • Java Stream流详解
  • 文心一言 4.5 开源深度剖析:中文霸主登场,开源引擎重塑大模型生态
  • C++11 std::is_permutation:从用法到原理的深度解析
  • 什么是延迟双删
  • 算法训练营day18 530.二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236. 二叉树的最近公共祖先
  • 通过 ip a 查看网络接口名
  • 【算法】贪心算法:摆动序列C++
  • 2025js——面试题(8)-http
  • Linux 系统下的 Sangfor VDI 客户端安装与登录完全攻略 (CentOS、Ubuntu、麒麟全线通用)
  • 程序跑飞是什么?
  • 核电概念盘中异动,中核科技涨停引领板块热度
  • 物联网技术促进能量收集创新应用落地
  • 第一章编辑器开发基础第一节绘制编辑器元素_4输入字段(4/7)
  • 【一维 前缀和+差分】
  • 互斥锁与同步锁
  • IIS错误:Service Unavailable HTTP Error 503. The service is unavailable.
  • Unity Shader 预热与缓存优化
  • Unity中HumanBodyBones骨骼对照
  • 卡在“pycharm正在创建帮助程序目录”
  • 笔试——Day6
  • 达梦国产数据库安装