深入解析代理模式:原理、实现与应用场景
1. 代理模式核心概念解析代理模式Proxy Pattern是结构型设计模式中最具实用性的模式之一它通过引入代理对象来控制对原始对象的访问。这种控制在软件开发中极为常见比如远程方法调用RMI的stub对象、Hibernate的延迟加载机制、Spring AOP的实现等本质上都是代理模式的应用。1.1 模式定义与基本结构代理模式的官方定义是为其他对象提供一种代理以控制对这个对象的访问。在实际编码中代理类和被代理类通常实现相同的接口客户端通过代理类间接访问被代理类。这种设计带来的直接好处是可以在不修改原始类代码的情况下通过代理类增加额外的功能逻辑。典型的代理模式包含三个核心角色Subject抽象主题声明真实主题和代理主题的共同接口RealSubject真实主题真正执行业务逻辑的具体类Proxy代理持有真实主题的引用控制对真实主题的访问// 抽象主题 interface Image { void display(); } // 真实主题 class RealImage implements Image { private String filename; public RealImage(String filename) { this.filename filename; loadFromDisk(); } private void loadFromDisk() { System.out.println(Loading filename); } Override public void display() { System.out.println(Displaying filename); } } // 代理 class ProxyImage implements Image { private RealImage realImage; private String filename; public ProxyImage(String filename) { this.filename filename; } Override public void display() { if (realImage null) { realImage new RealImage(filename); } realImage.display(); } }1.2 代理模式的五种常见类型根据使用场景和实现方式的不同代理模式可以分为以下几种主要类型虚拟代理Virtual Proxy延迟创建开销大的对象如图片懒加载。只有当真正需要时才创建真实对象优化系统资源使用。远程代理Remote Proxy为位于不同地址空间的对象提供本地代表。典型的如RPC框架中的stub对象隐藏了网络通信细节。保护代理Protection Proxy控制对原始对象的访问权限。常用于权限校验场景比如Spring Security的拦截机制。智能引用代理Smart Reference Proxy在访问对象时执行额外操作如引用计数、对象持久化等。C中的智能指针就是典型应用。动态代理Dynamic Proxy运行时动态生成代理类。Java中的java.lang.reflect.Proxy和CGLib都属于此类。提示在实际项目中动态代理的使用频率远高于静态代理因为它避免了为每个类手动编写代理类的繁琐工作。Spring框架的AOP功能就是基于动态代理实现的。2. 代理模式的实现方式详解2.1 静态代理实现静态代理是最基础的代理实现方式需要显式定义代理类。下面以文件加载为例展示完整实现// 文件加载器接口 public interface FileLoader { String load(String filePath); void save(String filePath, String content); } // 真实文件加载器 public class RealFileLoader implements FileLoader { Override public String load(String filePath) { System.out.println(Loading file: filePath); // 实际文件读取逻辑... return File content; } Override public void save(String filePath, String content) { System.out.println(Saving to file: filePath); // 实际文件写入逻辑... } } // 代理文件加载器添加权限校验 public class SecuredFileLoaderProxy implements FileLoader { private FileLoader fileLoader; private String userRole; public SecuredFileLoaderProxy(FileLoader fileLoader, String userRole) { this.fileLoader fileLoader; this.userRole userRole; } Override public String load(String filePath) { if (!checkAccess(read)) { throw new SecurityException(No read permission); } return fileLoader.load(filePath); } Override public void save(String filePath, String content) { if (!checkAccess(write)) { throw new SecurityException(No write permission); } fileLoader.save(filePath, content); } private boolean checkAccess(String operation) { // 简化版权限检查 return admin.equals(userRole) || (read.equals(operation) user.equals(userRole)); } }静态代理的优缺点非常明显优点实现简单容易理解缺点每个真实类都需要对应一个代理类当系统规模变大时会产生大量类文件2.2 动态代理实现动态代理通过在运行时生成代理类的方式解决了静态代理的缺点。Java提供了两种主要的动态代理机制2.2.1 JDK动态代理JDK动态代理基于接口实现使用java.lang.reflect.Proxy类创建代理对象public class LoggingInvocationHandler implements InvocationHandler { private Object target; public LoggingInvocationHandler(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start System.currentTimeMillis(); Object result method.invoke(target, args); long duration System.currentTimeMillis() - start; System.out.printf(Method %s executed in %d ms%n, method.getName(), duration); return result; } public static T T createProxy(T target, ClassT interfaceType) { return (T) Proxy.newProxyInstance( interfaceType.getClassLoader(), new Class?[]{interfaceType}, new LoggingInvocationHandler(target) ); } } // 使用示例 FileLoader loader new RealFileLoader(); FileLoader proxy LoggingInvocationHandler.createProxy(loader, FileLoader.class); proxy.load(/path/to/file);JDK动态代理的特点只能代理接口无需依赖第三方库性能较好相比CGLib2.2.2 CGLib动态代理CGLib通过继承方式实现代理可以代理普通类public class CglibLoggingInterceptor implements MethodInterceptor { Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { long start System.currentTimeMillis(); Object result proxy.invokeSuper(obj, args); long duration System.currentTimeMillis() - start; System.out.printf(CGLib - Method %s executed in %d ms%n, method.getName(), duration); return result; } public static T T createProxy(ClassT targetClass) { Enhancer enhancer new Enhancer(); enhancer.setSuperclass(targetClass); enhancer.setCallback(new CglibLoggingInterceptor()); return (T) enhancer.create(); } } // 使用示例 RealFileLoader proxy CglibLoggingInterceptor.createProxy(RealFileLoader.class); proxy.load(/path/to/file);CGLib的特点可以代理普通类需要引入第三方库生成代理类的过程较慢但执行效率高不能代理final类和方法注意Spring框架在选择代理方式时遵循这样的规则 - 如果目标对象实现了接口默认使用JDK动态代理如果没有实现接口则使用CGLib。可以通过配置强制使用CGLib。3. 代理模式的高级应用场景3.1 在Spring框架中的应用Spring框架中代理模式的应用无处不在最典型的就是AOP面向切面编程的实现。Spring AOP使用代理模式来实现横切关注点如事务管理、日志记录等与业务逻辑的分离。Aspect Component public class TransactionAspect { Autowired private PlatformTransactionManager transactionManager; Around(annotation(org.springframework.transaction.annotation.Transactional)) public Object manageTransaction(ProceedingJoinPoint pjp) throws Throwable { TransactionStatus status transactionManager.getTransaction( new DefaultTransactionDefinition()); try { Object result pjp.proceed(); transactionManager.commit(status); return result; } catch (Exception e) { transactionManager.rollback(status); throw e; } } }Spring处理事务的典型流程检查方法是否有Transactional注解创建代理对象JDK或CGLib在方法调用前后添加事务管理逻辑委托给原始对象执行业务方法3.2 MyBatis中的延迟加载MyBatis使用代理模式实现关联对象的延迟加载Lazy Loading。当访问一个对象的关联属性时MyBatis会返回一个代理对象只有在真正访问关联对象属性时才会执行SQL查询。public class Blog { private Integer id; private String title; private Author author; // 关联对象 // getters and setters } // 配置延迟加载 resultMap idblogResultMap typeBlog association propertyauthor columnauthor_id selectselectAuthor fetchTypelazy/ /resultMap实现原理MyBatis创建Blog对象的代理通常是Javassist或CGLib当调用getAuthor()时代理对象检查是否已加载如果未加载执行预定义的SQL查询selectAuthor返回真实的Author对象3.3 RPC框架中的远程代理远程方法调用RPC框架如Dubbo、gRPC都大量使用代理模式。客户端通过接口调用方法时实际上调用的是本地代理对象stub代理对象负责将方法调用信息序列化并通过网络发送到服务端。// 定义服务接口 public interface UserService { User getUserById(long id); } // 客户端调用 UserService userService Proxy.newProxyInstance( UserService.class.getClassLoader(), new Class[]{UserService.class}, new RemoteInvocationHandler(user-service) ); User user userService.getUserById(123);远程代理的核心工作流程客户端调用代理对象方法代理对象将方法名、参数等序列化通过网络发送到服务端服务端反序列化并调用真实方法将结果序列化返回客户端代理对象反序列化结果并返回4. 代理模式的性能优化与实践技巧4.1 代理对象的缓存策略频繁创建代理对象会产生性能开销合理的缓存策略可以显著提升性能public class ProxyCache { private static final MapObject, Object proxyCache new ConcurrentHashMap(); SuppressWarnings(unchecked) public static T T getCachedProxy(T target, ClassT interfaceType) { return (T) proxyCache.computeIfAbsent(target, k - LoggingInvocationHandler.createProxy(k, interfaceType)); } }缓存策略选择建议对于无状态对象可以使用全局缓存对于有状态对象需要考虑线程安全问题缓存键设计通常使用目标对象接口类型的组合4.2 选择性代理方法不是所有方法都需要代理可以通过注解或配置实现选择性代理Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface Loggable { } public class SelectiveInvocationHandler implements InvocationHandler { // ...其他代码 Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.isAnnotationPresent(Loggable.class)) { // 执行代理逻辑 long start System.currentTimeMillis(); Object result method.invoke(target, args); long duration System.currentTimeMillis() - start; System.out.printf(Method %s executed in %d ms%n, method.getName(), duration); return result; } // 直接调用原始方法 return method.invoke(target, args); } }4.3 代理链与责任链模式结合多个代理可以形成链式调用每个代理处理不同的横切关注点public class ChainedInvocationHandler implements InvocationHandler { private final ListInvocationHandler handlers; private final Object target; private int index 0; public ChainedInvocationHandler(Object target, ListInvocationHandler handlers) { this.target target; this.handlers new ArrayList(handlers); } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (index handlers.size()) { return handlers.get(index).invoke(proxy, method, args); } return method.invoke(target, args); } } // 使用示例 ListInvocationHandler handlers Arrays.asList( new LoggingInvocationHandler(target), new TransactionInvocationHandler(target), new CacheInvocationHandler(target) ); FileLoader proxy (FileLoader) Proxy.newProxyInstance( FileLoader.class.getClassLoader(), new Class[]{FileLoader.class}, new ChainedInvocationHandler(target, handlers) );这种设计类似于Servlet Filter或Spring Interceptor可以实现各种横切关注点的灵活组合。5. 常见问题与解决方案5.1 代理对象导致的equals/hashCode问题当使用代理对象时equals和hashCode方法的行为可能会出乎意料RealFileLoader loader1 new RealFileLoader(); RealFileLoader loader2 new RealFileLoader(); FileLoader proxy1 createProxy(loader1); FileLoader proxy2 createProxy(loader2); System.out.println(proxy1.equals(proxy2)); // 可能返回false System.out.println(loader1.equals(loader2)); // 可能返回true解决方案在代理类中重写equals和hashCode委托给目标对象使用工具类比较非代理对象public static boolean equals(Object a, Object b) { if (a b) return true; if (a null || b null) return false; Object aTarget getTarget(a); Object bTarget getTarget(b); return aTarget.equals(bTarget); } private static Object getTarget(Object obj) { if (Proxy.isProxyClass(obj.getClass())) { InvocationHandler handler Proxy.getInvocationHandler(obj); if (handler instanceof TargetAware) { return ((TargetAware) handler).getTarget(); } } return obj; }5.2 循环依赖问题当代理对象相互引用时可能导致栈溢出public class ServiceA { Autowired private ServiceB serviceB; } public class ServiceB { Autowired private ServiceA serviceA; }解决方案使用setter注入代替字段注入配置Spring使用三级缓存解决循环依赖对部分bean禁用代理Scope(proxyMode ScopedProxyMode.NO)5.3 动态代理的调试技巧动态代理类在调试时难以跟踪可以采用以下技巧输出代理类名称System.out.println(proxy.getClass().getName()); // 输出类似com.sun.proxy.$Proxy0保存生成的代理类到文件仅JDK动态代理System.getProperties().put(sun.misc.ProxyGenerator.saveGeneratedFiles, true);使用Arthas等Java诊断工具检查代理类# 查看类继承关系 sc -d com.sun.proxy.$Proxy0 # 查看方法调用轨迹 trace com.sun.proxy.$Proxy0 *5.4 性能优化建议缓存代理类动态生成代理类开销较大应该缓存已生成的代理类减少代理层级避免多层代理嵌套如Spring AOP的多次代理选择性代理只为必要的方法添加代理逻辑使用轻量级字节码工具如ByteBuddy相比CGLib有更好的性能// ByteBuddy创建代理示例 Class? dynamicType new ByteBuddy() .subclass(Object.class) .method(ElementMatchers.named(toString)) .intercept(FixedValue.value(Hello World!)) .make() .load(getClass().getClassLoader()) .getLoaded();代理模式在实际项目中几乎无处不在从底层的RPC调用到上层的业务逻辑处理都能看到它的身影。掌握代理模式不仅能够更好地理解框架原理还能在需要扩展系统功能时提供灵活的解决方案。特别是在面对遗留系统改造时代理模式往往能在不修改原有代码的情况下实现功能增强这充分体现了开闭原则的价值。

相关新闻

StreamCap:3步解决跨平台直播录制难题的完整方案

StreamCap:3步解决跨平台直播录制难题的完整方案

StreamCap:3步解决跨平台直播录制难题的完整方案 【免费下载链接】StreamCap Multi-Platform Live Stream Automatic Recording Tool | 多平台直播流自动录制客户端 基于FFmpeg 支持监控/定时/转码 项目地址: https://gitcode.com/gh_mirrors/st/StreamCap …

2026/10/19 11:21:18 阅读更多 →
国产数据库性能监控探针升级实践与优化

国产数据库性能监控探针升级实践与优化

1. 项目背景与核心价值 POne性能测试平台作为企业级应用性能评估的核心工具,其监控探针的数据库兼容性直接影响着测试数据的完整性和准确性。近期完成的监控探针升级,新增了对东方通TongRDS和达梦DM8两款主流信创数据库的深度支持,这标志着国…

2026/10/20 6:04:52 阅读更多 →
QKeyMapper:免费开源按键映射工具,让Windows操作更高效

QKeyMapper:免费开源按键映射工具,让Windows操作更高效

QKeyMapper:免费开源按键映射工具,让Windows操作更高效 【免费下载链接】QKeyMapper [按键映射工具] QKeyMapper,Qt开发Win10&Win11可用,不修改注册表、不需重新启动系统,可立即生效和停止。支持游戏手柄映射到键鼠…

2026/10/18 4:44:41 阅读更多 →

最新新闻

CRC计算源码选型指南:参数模型、查表法与逐位法实现及避坑实践

CRC计算源码选型指南:参数模型、查表法与逐位法实现及避坑实践

简介:这份循环冗余校验码(CRC)计算源代码合集面向嵌入式开发、通信协议实现与数据校验方向的学习者,帮助理解并落地CRC-8、CRC-16、CRC-32等常见算法的软件实现与硬件映射思路。压缩包共1220个文件,约6.65MB&#xff0…

2026/10/20 6:04:24 阅读更多 →
E22-400T22D LoRa模块实战:配置、距离测试与避坑指南

E22-400T22D LoRa模块实战:配置、距离测试与避坑指南

做物联网设备这行,无线通信方案永远是绕不开的一道坎。这篇要聊的是一款非常经典的E22-400T22D LoRa模块,很多朋友在户外采集、农业监测、机房环境监控这类项目里都会遇到它。市面上那么多无线模块,为什么LoRa方案能站住脚,这款22…

2026/10/20 6:04:24 阅读更多 →
AI帮不了你洗澡:从具身认知到智能浴室的真实边界

AI帮不了你洗澡:从具身认知到智能浴室的真实边界

前几天晚上泡澡,我盯着天花板突发奇想,拿起手机对某AI助手说了一句:“既然你这么聪明,能不能帮我把澡洗了?”屏幕那头沉默了两秒,然后弹出一篇礼貌又工整的《安全洗澡指南》,从水温控制到防滑垫…

2026/10/20 6:04:24 阅读更多 →
2026年AI玩具十大榜单:从智能交互到系统化成长

2026年AI玩具十大榜单:从智能交互到系统化成长

2026年,随便点开一个电商平台,搜索“儿童机器人”“智能玩具”,结果能翻出几十页。每张详情页都在讲“AI大模型”“智能对话”“个性化成长”,可真正买回家,大多数产品第三天就变成了吃灰的塑料摆件。这份榜单的由来&a…

2026/10/20 6:04:24 阅读更多 →
经典ASP作业提交系统源码解析:环境搭建、核心模块与避坑指南

经典ASP作业提交系统源码解析:环境搭建、核心模块与避坑指南

简介:这份资源是面向高校学生与Web开发初学者的ASP网上作业提交系统课程设计资料,围绕网络教育平台中作业提交与自适应学习导航的实际需求展开。内容以ASP结合ACCESS数据库为核心,涵盖模块导航、知识点检索导航、知识点关联导航与帮助导航等设…

2026/10/20 6:04:24 阅读更多 →
从单体到弹性扩展:高并发架构改造的完整实战复盘

从单体到弹性扩展:高并发架构改造的完整实战复盘

1. 项目背景与架构需求拆解1.1 最初的系统,和它一步步暴露出的问题先说下背景。这个代号为"arch-system"的架构改造项目,源于团队负责的一条核心业务链路。最初的系统是典型的三层单体:一个应用服务承载所有接口,MySQL …

2026/10/20 6:03:23 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →