Java动态代理入门:在运行时凭空捏造一个中介
Java动态代理入门在运行时凭空捏造一个中介⚠️前置知识提示本篇会大量用到反射相关知识如Proxy.newProxyInstance、Method.invoke、Class.getInterfaces等。如果你还不了解反射建议先看《Java反射入门在运行时看透一个类》。假设你是一个大明星真实目标粉丝调用者想找你开演唱会。但粉丝不能直接联系你——他们要先找你的经纪公司动态代理签合同、交钱然后经纪公司安排你上台唱歌唱完后再负责结账和公关。粉丝全程以为自己在跟经纪公司打交道而你只管安心唱歌不用操心任何杂事。这个经纪公司就是动态代理。一、什么是动态代理代理Proxy这个词在生活中很常见房产中介、旅行社、代购……它们的核心作用都是在客户和目标之间充当中间人。在 Java 中代理分为两种类型特点生成时机静态代理程序员手动写一个代理类编译前动态代理程序在运行时自动生成代理类运行时动态代理的神奇之处在于你不需要事先写好代理类的代码Java 会在程序运行的过程中临时凭空捏造出一个代理对象拦截外界对真实目标的访问在目标干活前后自动加上额外的功能。为什么叫动态因为代理类不是写死在.java文件里的而是在 JVM 运行期间动态生成字节码并加载到内存中的。它像是一个临时工项目需要时招进来干完活就释放。二、动态代理的作用动态代理的核心价值可以用一句话概括在不修改原代码的前提下为目标对象增加额外功能。典型应用场景场景动态代理做什么日志记录在方法执行前后自动打印日志权限校验在方法执行前检查用户是否有权限事务管理在方法执行前开启事务执行后提交或回滚性能监控统计每个方法的执行耗时远程调用RPC 框架中代理对象负责网络通信让调用者像调用本地方法一样调用远程服务为什么不用直接修改原代码假设你有 100 个业务方法每个方法都要加日志。如果直接改源码你得改 100 处。而且日志逻辑和业务逻辑混在一起代码越来越臃肿。用动态代理日志逻辑写一次通过代理统一加在所有方法上。业务代码保持干净日志代码也独立可复用。三、JDK 动态代理详解Java 内置了一套动态代理机制核心是两个类java.lang.reflect.Proxy提供创建代理对象的静态方法java.lang.reflect.InvocationHandler接口定义代理对象要执行的拦截逻辑。3.1 需求场景我们来模拟一个旅行社系统TravelAgency旅行社接口定义了办理签证和旅游两个服务Traveler游客真实的目标对象实现了旅行社接口ProxyUtils代理工具类负责生成代理对象在游客办事前后加上额外服务收资料、送礼品等。3.2 定义接口publicinterfaceTravelAgency{BooleanprocessVisa(Stringarea);// 办理签证voidtravel();// 出去旅游}3.3 定义真实目标类importlombok.Data;importlombok.AllArgsConstructor;importlombok.NoArgsConstructor;Data// Lombok自动生成 getter、setter、toString 等方法AllArgsConstructor// Lombok自动生成全参构造器NoArgsConstructor// Lombok自动生成无参构造器publicclassTravelerimplementsTravelAgency{Stringname;Stringnumber;OverridepublicBooleanprocessVisa(Stringarea){if(number!null){System.out.println(this.name通过号码this.number办理了area签证);returntrue;}System.out.println(this.name办理签证失败没有预留号码);returnfalse;}Overridepublicvoidtravel(){System.out.println(this.name出去旅游了玩的很开心);}}3.4 创建代理工具类importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.lang.reflect.Proxy;publicclassProxyUtils{/** * 为指定的 Traveler 创建一个代理对象 */publicstaticTravelAgencycreateProxy(Travelertraveler){/** * Proxy.newProxyInstance 是 JDK 动态代理的核心方法需要三个参数 * * 参数一ClassLoader类加载器 * 用于加载动态生成的代理类。通常用当前类的类加载器。 * * 参数二Class[]接口数组 * 指定代理类需要实现哪些接口。这里让代理类实现 TravelAgency 接口 * 这样代理对象才能被当作 TravelAgency 类型来使用。 * * 参数三InvocationHandler调用处理器 * 这是一个接口里面定义了 invoke 方法。当外界调用代理对象的任何方法时 * 都会先进入这个 invoke 方法由我们来决定怎么代理。 */return(TravelAgency)Proxy.newProxyInstance(ProxyUtils.class.getClassLoader(),// 参数一类加载器traveler.getClass().getInterfaces(),// 参数二目标类实现的接口newInvocationHandler(){// 参数三调用处理器/** * 当外界调用代理对象的任何方法时都会进入这个方法。 * * param proxy 代理对象本身通常用不到 * param method 当前正在被调用的方法对象 * param args 当前方法接收到的参数 * return 方法的返回值会返回给调用者 */OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{StringmethodNamemethod.getName();// 前置增强方法执行前做一些额外操作 if(processVisa.equals(methodName)){System.out.println(【代理】旅行社正在为您收集签证资料...);}elseif(travel.equals(methodName)){System.out.println(【代理】旅行社正在为您预定机票和酒店...);}// 调用真实目标对象的方法 // method.invoke(traveler, args) 就是通过反射调用 traveler 的对应方法Objectresultmethod.invoke(traveler,args);// 后置增强方法执行后做一些额外操作 if(processVisa.equals(methodName)){System.out.println(【代理】签证资料已提交大使馆请耐心等待。);}elseif(travel.equals(methodName)){System.out.println(【代理】旅行结束欢迎下次光临送您一份纪念品);}returnresult;}});}}3.5 测试运行publicclassProxyTest{publicstaticvoidmain(String[]args){// 创建真实目标对象小红没有预留号码TravelertravelernewTraveler(小红,null);// 为小红创建一个代理对象TravelAgencyproxyProxyUtils.createProxy(traveler);// 通过代理对象办理签证Booleanflagproxy.processVisa(泰国);// 如果签证办理成功继续旅游if(flag){proxy.travel();}}}输出【代理】旅行社正在为您收集签证资料... 小红办理签证失败没有预留号码 【代理】签证资料已提交大使馆请耐心等待。如果把null改成13800138000输出会变成【代理】旅行社正在为您收集签证资料... 小红通过号码13800138000办理了泰国签证 【代理】签证资料已提交大使馆请耐心等待。 【代理】旅行社正在为您预定机票和酒店... 小红出去旅游了玩的很开心 【代理】旅行结束欢迎下次光临送您一份纪念品3.6 动态代理的执行流程调用者 ──→ proxy.processVisa(泰国) │ ▼ 【进入 InvocationHandler.invoke()】 │ ├── 前置增强打印收集签证资料... │ ├── 调用真实目标traveler.processVisa(泰国) │ └── 小红通过号码...办理了泰国签证 │ ├── 后置增强打印资料已提交... │ └── 返回结果 true │ 回到调用者拿到返回值 true关键理解调用者以为自己在调用proxy.processVisa()但实际上所有调用都被invoke()方法拦截了。invoke()就像一个收费站每辆车方法调用经过都要被检查和处理。四、动态代理的局限性只能代理接口JDK 动态代理有一个前提条件目标类必须实现了接口。因为Proxy.newProxyInstance的第二个参数要求传入接口数组生成的代理类也只能是实现这些接口。如果目标类没有实现任何接口JDK 动态代理就无能为力了。那怎么办——CGLIB对于没有实现接口的类可以使用CGLIBCode Generation Library。CGLIB 的原理和 JDK 动态代理不同它不是基于接口而是基于继承。CGLIB 会动态生成一个目标类的子类并重写其中的方法在重写的方法里插入增强逻辑。// CGLIB 的伪代码示意publicclassTraveler$$EnhancerByCGLIBextendsTraveler{Overridepublicvoidtravel(){System.out.println(【CGLIB前置增强】);// 前置逻辑super.travel();// 调用父类真实目标的方法System.out.println(【CGLIB后置增强】);// 后置逻辑}}简单记忆JDK 动态代理基于接口目标类必须有接口CGLIB基于继承目标类不需要接口但不能是final类因为 final 类不能被继承。Spring 框架的 AOP 默认策略是如果目标类有接口就用 JDK 动态代理如果没有接口就自动切换到 CGLIB。五、从动态代理到 AOP面向切面编程动态代理是 Java 语言层面的技术而AOPAspect Oriented Programming面向切面编程是一种编程思想。两者的关系就像发动机和汽车——动态代理是发动机AOP 是整辆汽车。5.1 什么是 AOP传统的 OOP面向对象编程把功能封装在类里但有一些功能不属于任何一个类而是横跨多个类的——比如日志、事务、权限检查。AOP 的思路是把这些横跨多个类的功能抽出来统一放在一个切面里然后自动织入到需要的地方。5.2 AOP 的核心术语术语通俗解释类比切面Aspect横切关注点的模块化日志切面、事务切面连接点Join Point程序执行过程中的某个点可以被切面切入方法调用、异常抛出切入点Pointcut定义哪些连接点会被切入execution(* com.service.*.*(..))通知Advice切面在连接点上执行的动作前置通知、后置通知、环绕通知织入Weaving把切面应用到目标对象的过程编译时织入、运行时织入5.3 AOP 与动态代理的关系Spring AOP 的底层实现就是动态代理当目标类有接口时Spring 使用JDK 动态代理当目标类没有接口时Spring 使用CGLIB。你在 Spring 中写的Aspect、Before、After、Around等注解最终都会被 Spring 翻译成动态代理的代码。5.4 学习展望理解了动态代理你就掌握了 AOP 的底层原理。下一步可以学习Spring AOP用声明式的方式Aspect、Before、Around实现切面不再需要手写Proxy.newProxyInstanceCGLIB 深入了解 CGLIB 如何通过 ASM 字节码操作生成子类RPC 框架如 Dubbo客户端通过动态代理调用远程服务像调用本地方法一样简单。一句话总结动态代理让你可以在不修改原代码的情况下增强功能AOP 让你可以用更优雅的方式组织这些增强逻辑。写在最后本篇我们从经纪公司的比喻出发学习了 JDK 动态代理的完整用法。动态代理篇重点回顾动态代理在运行时临时生成代理类像中间人一样拦截方法调用核心 APIProxy.newProxyInstance(类加载器, 接口数组, InvocationHandler)InvocationHandler.invoke()是拦截的入口可以在方法调用前后做前置增强和后置增强JDK 动态代理只能代理实现了接口的类没有接口的类需要用 CGLIB基于继承AOP 是编程思想Spring AOP 的底层就是动态代理 CGLIB动态代理是日志、事务、权限、RPC等技术的基石。到这里反射 → 注解 → 动态代理三篇博客就全部完成了。这三者的关系层层递进注解提供标记反射提供读取和操作能力动态代理利用前两者实现无侵入式增强——这正是 Java 框架设计的核心套路。

相关新闻

Android开发前沿技术与最佳实践解析

Android开发前沿技术与最佳实践解析

1. Android Weekly Notes Issue #227 项目概述Android Weekly Notes 是一个持续更新的技术笔记系列,专注于记录和分享每期Android Weekly技术周刊中的核心内容。Issue #227作为该系列的最新一期,聚焦于Android开发领域的前沿动态、工具更新和最佳实践。这…

2026/10/2 1:06:45 阅读更多 →
【C++】C++11:lambda、包装器

【C++】C++11:lambda、包装器

C11&#xff1a;lambda、包装器 1、lambda表达式 1.1 lambda表达式语法 lambda 表达式本质是一个匿名函数对象&#xff0c;跟普通函数不同的是他可以定义在函数内部。我们⼀般是用auto或者模板参数定义的对象去接收lambda 对象。 #include <iostream> using namespace st…

2026/9/29 22:00:47 阅读更多 →
机器学习模型生产化:从部署到高可用的四大工程支柱

机器学习模型生产化:从部署到高可用的四大工程支柱

1. 项目概述&#xff1a;当模型走出笔记本&#xff0c;真正开始“呼吸”现实世界你有没有经历过这样的时刻&#xff1f;模型在 Jupyter Notebook 里跑得飞起&#xff0c;AUC 0.92&#xff0c;F1 0.88&#xff0c;交叉验证稳如老狗&#xff1b;团队围在白板前击掌庆祝&#xff0…

2026/10/4 1:11:38 阅读更多 →

最新新闻

从点云到地图:扫地机器人SLAM与Nav2导航全链路

从点云到地图:扫地机器人SLAM与Nav2导航全链路

从点云到地图&#xff1a;一台扫地机器人的SLAM与Nav2导航全链路做扫地机器人这件事&#xff0c;听起来好像就是把激光雷达装上、跑个SLAM、再给个目标点让它自己走。真做起来你才会发现&#xff0c;从传感器拿到的原始点云到最终机器人在地图里自由穿梭&#xff0c;中间隔着一…

2026/10/4 11:00:33 阅读更多 →
零部署接入大模型:CubeStudio让Label Studio自动预标注不再写后端

零部署接入大模型:CubeStudio让Label Studio自动预标注不再写后端

做数据标注的朋友应该都有体会&#xff1a;最耗人的不是平台本身&#xff0c;而是每一批数据都要从零开始标。我之前在一个文本分类项目上卡了小两周&#xff0c;几万条用户评论要人工打标签&#xff0c;后来换成预标注流程&#xff0c;一天就把初标做完了。这里的核心操作就是…

2026/10/4 11:00:33 阅读更多 →
微信小游戏 Unity 适配方案:ProfilingMemory 内存分析工具实战指南

微信小游戏 Unity 适配方案:ProfilingMemory 内存分析工具实战指南

游戏开发移动开发WebAssembly 【免费下载链接】minigame-unity-webgl-transform 微信小游戏Unity引擎适配器文档。 项目地址&#xff1a; https://gitcode.com/GitHub_Trending/mi/minigame-unity-webgl-transform 点击查看 免费下载 导读&#xff1a;本文以微信小游戏 Unity …

2026/10/4 11:00:33 阅读更多 →
从泄露的 Wrtn GPT-4 系统提示词看韩语 AI 助手的提示词工程与防泄露设计

从泄露的 Wrtn GPT-4 系统提示词看韩语 AI 助手的提示词工程与防泄露设计

人工智能大模型提示工程 【免费下载链接】leaked-system-prompts Collection of leaked system prompts 项目地址&#xff1a; https://gitcode.com/GitHub_Trending/le/leaked-system-prompts 点击查看 免费下载 导读&#xff1a;本文以仓库内 wrtn-gpt4_20240215.md 记录的 …

2026/10/4 11:00:33 阅读更多 →
AI Agent Harness 工程实践:从 Agent Loop 到 MCP 接入与故障排查

AI Agent Harness 工程实践:从 Agent Loop 到 MCP 接入与故障排查

1. 从 Trae Work 说起&#xff1a;为什么 AI Agent 的 Harness 值得单独聊第一次在 Trae Work 里把一个小任务跑通的时候&#xff0c;我盯着屏幕上那条不断滚动的执行日志看了很久。它不是在“聊天”&#xff0c;而是在“干活”——读文件、调工具、改代码、跑测试、根据报错回…

2026/10/4 11:00:33 阅读更多 →
STM32F411RE 驱动 MR25H40CDF MRAM 实战:SPI 配置与掉电保护

STM32F411RE 驱动 MR25H40CDF MRAM 实战:SPI 配置与掉电保护

1. 为什么 MRAM 在嵌入式存储里越来越受关注搞嵌入式的人都有一个共同的痛&#xff1a;选存储芯片的时候&#xff0c;Flash 写起来慢、擦写寿命有限&#xff0c;SRAM 速度快但一断电数据就没了&#xff0c;EEPROM 写入速度又上不去。早些年做工业数据采集&#xff0c;每次掉电前…

2026/10/4 10:59:32 阅读更多 →

日新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/4 1:00:58 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/4 1:00:58 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/4 1:00:58 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/4 1:00:58 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/4 1:00:58 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/4 1:00:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/2 10:36:31 阅读更多 →
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/4 9:43:54 阅读更多 →
黑夜航拍船只数据集训练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/3 9:42:36 阅读更多 →