Java之动态代理 DynamicProxy
动态代理概念在学习动态代理之前先了解静态代理可参考静态代理静态代理核心缺陷类爆炸一个接口实现类必须手写一个专属代理类业务类越多代理类文件成倍增加增强逻辑无法复用日志、耗时统计等通用逻辑在每个代理类重复复制修改需要改动全部代理接口迭代维护成本极高接口新增方法所有业务类、全部静态代理类必须同步重写极易漏改运行时无法灵活适配代理类编译前手写固定无法根据运行时参数动态切换代理目标。动态代理的核心设计目标不用手动编写代理类程序运行阶段通过反射 / 字节码技术自动生成代理对象统一拦截所有目标方法一套增强逻辑适配同一接口下全部实现类彻底解决静态代理所有短板。动态代理两大分类类型依赖限制条件底层原理适用场景JDK 动态代理JDK 原生反射包目标类必须实现接口运行时生成实现目标接口的代理类有接口的 Service、Mapper、Feign 远程接口CGLIB 动态代理第三方字节码包spring 内置目标类不能是 final方法不能 final运行时生成目标类的子类重写业务方法无接口的普通业务类本文章主要以JDK 动态代理讲解动态代理核心概念JDK 动态代理依靠java.lang.reflect反射包实现运行时动态生成实现目标接口的匿名代理类所有代理对象的方法调用都会统一转发至InvocationHandler的invoke方法拦截开发者在该方法统一编写前置 / 后置增强逻辑。三大核心 API1InvocationHandler拦截处理器核心public interface InvocationHandler { /** * 代理方法统一拦截入口 * param proxy 自动生成的代理对象 * param method 当前被调用的目标方法反射对象 * param args 调用方法传入的参数数组 * return 方法执行返回值 */ Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }invoke参数讲解proxy动态生成的代理对象本身极少使用仅用于区分代理实例method本次触发调用的方法反射对象唯一标识当前执行的业务方法args调用方法传入的参数无参方法则为null。2Proxy代理生成工具类public static Object newProxyInstance(ClassLoader loader, Class?[] interfaces, InvocationHandler h)ClassLoader loader类加载器使用目标对象的类加载器用于加载动态生成的代理类Class?[] interfaces目标类实现的接口数组代理类会全部实现这些接口保证多态互换InvocationHandler h自定义拦截处理器绑定到代理对象所有方法调用转发至该处理器。3Method反射方法对象封装当前调用的方法信息方法名、参数、返回值、所属类用于反射执行真实目标方法。结合代码彻底理解package demo1.basic; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Scanner; public class DynamicAgent { public static void main(String[] args) { Scanner scanner new Scanner(System.in); System.out.println( 动态代理测试菜单 ); System.out.println(1Kobe 2Jordan 3pyLiu); System.out.print(请选择球员); int choice scanner.nextInt(); NBA target null; switch (choice) { case 1: target new Kobe(); break; case 2: target new Jordan(); break; case 3: target new pyLiu(); break; default: System.out.println(无效选择); return; } // 绑定拦截处理器 DogSon dogSon new DogSon(); dogSon.getTarget(target); NBA proxy (NBA) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), dogSon ); // 测试1无参无返回值方法 personalAction() System.out.println(\n---------- 调用无参方法 personalAction ----------); proxy.personalAction(); // 测试2多参数、无返回值方法 score(int, String) System.out.println(\n---------- 调用双参数无返回方法 score ----------); proxy.score(81, 季后赛单场最高分); // 测试3带参数、有返回值方法 getCareerData(int) System.out.println(\n---------- 调用带参有返回方法 getCareerData ----------); String data proxy.getCareerData(2010); System.out.println(【主线程接收方法返回值】 data); } // 自定义拦截处理器 static class DogSon implements InvocationHandler { private NBA target; void getTarget(NBA target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName method.getName(); // 1、区分不同方法差异化前置增强 if (personalAction.equals(methodName)) { // 无参方法args null if (target instanceof Kobe) { System.out.print(What can I say ); } else if (target instanceof Jordan) { System.out.print(GOAT ); } else if (target instanceof pyLiu) { System.out.print(你抽中了隐藏款 ); } } else if (score.equals(methodName)) { // 带参方法打印args数组查看参数传递效果 System.out.print(【score方法前置】当前传入参数); for (Object arg : args) { System.out.print(arg ); } System.out.print( | 球员 target.getClass().getSimpleName() ); } else if (getCareerData.equals(methodName)) { System.out.print(【getCareerData方法前置】查询年份 args[0] ); } // 打印目标类名 System.out.println( target.getClass().getSimpleName()); // 2、反射执行业务方法 // 统一一行代码适配无参/多参、有返回/无返回所有方法 Object result method.invoke(target, args); // 3、后置增强处理返回值 if (result ! null) { System.out.println(【后置增强】业务方法返回内容 result); } else { System.out.println(【后置增强】该方法无返回值void); } return result; // 把业务返回值向上传递给调用方 } } // 统一业务接口3种方法覆盖 无参void、多参void、带参有返回值 interface NBA { // 无参数、无返回值 void void personalAction(); // 多个参数、无返回值 void void score(int point, String desc); // 单个参数、有返回值 String String getCareerData(int year); } // Kobe 实现类 static class Kobe implements NBA { Override public void personalAction() { System.out.println(湖人总冠军); } Override public void score(int point, String desc) { System.out.println(科比拿下 point 分记录 desc); } Override public String getCareerData(int year) { if (year 2010) { return 2010年科比拿下总冠军FMVP; } return 科比生涯数据; } } // Jordan 实现类 static class Jordan implements NBA { Override public void personalAction() { System.out.println(黑人乔丹); } Override public void score(int point, String desc) { System.out.println(乔丹拿下 point 分记录 desc); } Override public String getCareerData(int year) { return 乔丹6冠历史第一人; } } // pyLiu 实现类 static class pyLiu implements NBA { Override public void personalAction() { System.out.println(暴扣乔丹); } Override public void score(int point, String desc) { System.out.println(我大学室友 point 分记录 desc); } Override public String getCareerData(int year) { return 上理第一给; } } }建议粘贴到idea查看观察输入输出主要针对三种不同方案进行分别讲解公共前置流程程序启动控制台输入选择1创建被代理对象target new Kobe()创建拦截处理器实例DogSon dogSon new DogSon()将真实球员对象注入处理器dogSon.getTarget(target)调用Proxy.newProxyInstance()JVM 在内存自动生成动态代理类$Proxy0返回代理对象proxy走完以上步骤后分三条独立链路执行无参方法、有参方法、带返回值方法。1无参方法主线执行proxy.personalAction()自动生成的代理类$Proxy0捕获该方法调用封装三组参数proxy当前代理实例method对应personalAction()的反射方法对象argsnull无入参执行dogSon.invoke(proxy, method, null)进入拦截逻辑invoke 内部逻辑通过method.getName()判断当前是personalAction打印Kobe前置文案打印目标类名Kobe执行反射调用method.invoke(target, null)触发Kobe.personalAction()输出业务内容该方法是 void反射执行结果为null后置打印「无返回值」return null 逐层回传给主线程主线无变量接收2有参方法主线执行proxy.score(81, 季后赛单场最高分)代理类捕获调用封装三组参数proxy当前代理实例method对应score(int,String)的反射对象argsnew Object[]{81, 季后赛单场最高分}入参封装成数组执行dogSon.invoke(proxy, method, args)invoke 内部逻辑通过方法名识别为score循环遍历 args 数组打印所有传入参数打印目标类名Kobe执行method.invoke(target, args)数组内参数自动传给业务方法执行Kobe.score(81, 季后赛单场最高分)void 方法执行结果为 null后置打印「无返回值」return null 回传给主线程3有返回值方法主线执行String data proxy.getCareerData(2010)代理类捕获调用封装三组参数proxy当前代理实例method对应getCareerData(int)的反射对象argsnew Object[]{2010}执行dogSon.invoke(proxy, method, args)invoke 内部逻辑通过方法名识别为getCareerData读取args[0]获取查询年份打印前置日志打印目标类名Kobe执行method.invoke(target, args)调用Kobe.getCareerData(2010)得到返回字符串2010年科比拿下总冠军FMVP后置增强打印业务返回内容将字符串结果return向上传递主线程接收返回值存入变量data打印输出个人学习问题解惑1、代理类proxy在调用方法如.personalAction()时是如何把方法名传给拦截器DogSon中的invoke函数中的method简单来说就是我拦截器的invoke是怎么知道要执行personalAction()这个方法的通过查询在调用(NBA) Proxy.newProxyInstance时会在内存自动生成一个实现NBA接口的代理类这个类是看不见的伪代码如下// JDK自动生成的代理类 $Proxy0 implements NBA public class $Proxy0 implements NBA { // 构造时传入你写的DogSon拦截器 private final InvocationHandler h; public $Proxy0(InvocationHandler h) { this.h h; } // 重写接口无参方法 personalAction Override public void personalAction() { try { // 1. 提前预加载好当前方法的Method对象固定写死在代理类字节码里 Method m NBA.class.getDeclaredMethod(personalAction); // 2. 直接把method、代理对象、参数打包传给invoke h.invoke(this, m, null); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } // 重写接口有参方法 score Override public void score(int point, String desc) { try { Method m NBA.class.getDeclaredMethod(score, int.class, String.class); // 调用时传入的81、季后赛单场最高分 自动封装成数组args Object[] args new Object[]{point, desc}; h.invoke(this, m, args); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } // 重写带返回值方法 getCareerData Override public String getCareerData(int year) { try { Method m NBA.class.getDeclaredMethod(getCareerData, int.class); Object[] args new Object[]{year}; // 接收invoke返回的结果向上返回给主线程 Object res h.invoke(this, m, args); return (String) res; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } }可以看到隐式的代理类$Proxy0重写了NBA接口的所有方法显式的Proxy在构造时将拦截器InvocationHandler传给了隐式的代理类$Proxy0而$Proxy0中在每个方法里都指明了Method并在调用拦截器的invoke方法时将this也就是Proxymethodargs都传给了拦截器中的invoke方法用以执行。以上内容纯属个人经验帖欢迎讨论查漏补缺。

相关新闻

STM32 最小系统 PCB 设计对比:2层板与4层板在 EMC 和成本上的 3 点差异

STM32 最小系统 PCB 设计对比:2层板与4层板在 EMC 和成本上的 3 点差异

STM32最小系统PCB设计进阶:2层板与4层板在EMC与成本上的深度解析1. 硬件设计中的层数选择困境当STM32F103最小系统遇上流水灯这种基础功能时,硬件工程师常陷入一个经典抉择:该用2层板还是4层板?这个看似简单的选择背后&#xff0c…

2026/7/9 8:15:19阅读更多 →
品牌为什么总在季前埋下库存风险?关键在商品计划和OTB预算

品牌为什么总在季前埋下库存风险?关键在商品计划和OTB预算

服装行业有个普遍现象:季末复盘时,滞销款库存和畅销款断货同时出现。很多品牌把原因归到季中执行——补货慢、调拨不及时、门店预测不准。但如果回到季前那个时点看,真正的问题往往在商品计划和OTB预算阶段就已经埋下。季前商品计划解决的是”…

2026/7/9 8:15:19阅读更多 →
JetBrains IDE试用期重置终极指南:如何轻松恢复30天免费使用权

JetBrains IDE试用期重置终极指南:如何轻松恢复30天免费使用权

JetBrains IDE试用期重置终极指南:如何轻松恢复30天免费使用权 【免费下载链接】ide-eval-resetter 项目地址: https://gitcode.com/gh_mirrors/id/ide-eval-resetter 还在为IntelliJ IDEA、PyCharm、WebStorm等JetBrains IDE试用期到期而烦恼吗&#xff1f…

2026/7/9 8:10:18阅读更多 →
京东app 滑块验证码逆向分析

京东app 滑块验证码逆向分析

声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!有相关问题请第一时间点击头像看简介或私…

2026/7/9 9:15:27阅读更多 →
性价比高的电商客服外包公司

性价比高的电商客服外包公司

在电商行业蓬勃发展的当下,越来越多的电商商家意识到专业客服对于业务发展的重要性。然而,组建一支专业的客服团队不仅需要投入大量的时间和资金,还面临着管理等诸多难题。因此,选择一家性价比高的电商客服外包公司成为了众多商家…

2026/7/9 9:15:27阅读更多 →
Next.js - 01 - 路由、特殊文件与渲染机制

Next.js - 01 - 路由、特殊文件与渲染机制

路由、特殊文件与渲染机制 npm 命令对照命令实际执行用途npm run devnext dev开发模式:热更新、详细报错npm run buildnext build先构建生产版本npm startnext start生产模式:跑已经 build 好的代码使用场景 日常写代码、调试 → 用 npm run dev模拟线上…

2026/7/9 9:15:27阅读更多 →
从安装到实战:掌握AI编程助手Claude Code的高效协作心法

从安装到实战:掌握AI编程助手Claude Code的高效协作心法

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 最近在折腾一些本地代码生成和辅助编程的工具,发现一个挺有意思的现象:很多开发者,包括我自己在内…

2026/7/9 9:15:27阅读更多 →
MyComputerManager:5分钟彻底清理Windows“此电脑“中的顽固图标

MyComputerManager:5分钟彻底清理Windows“此电脑“中的顽固图标

MyComputerManager:5分钟彻底清理Windows"此电脑"中的顽固图标 【免费下载链接】MyComputerManager 管理“此电脑”里删不掉的流氓“快捷方式”(包括侧边栏),同时可自己添加这类“快捷方式” 项目地址: https://gitco…

2026/7/9 9:15:27阅读更多 →
3个超实用秘籍:用开源编辑器彻底改变你的暗黑2游戏体验

3个超实用秘籍:用开源编辑器彻底改变你的暗黑2游戏体验

3个超实用秘籍:用开源编辑器彻底改变你的暗黑2游戏体验 【免费下载链接】d2s-editor 项目地址: https://gitcode.com/gh_mirrors/d2/d2s-editor 还记得那个深夜吗?你在暗黑破坏神2中奋战了整整8个小时,只为了一件传说中的暗金装备&am…

2026/7/9 9:10:27阅读更多 →
从GitHub安全案例解析常见漏洞与防护实践

从GitHub安全案例解析常见漏洞与防护实践

1. 项目概述:从GitHub Trending看安全实战 最近在GitHub Trending上看到一个项目,叫 skills4/skills ,它因为一些安全漏洞案例被大家讨论。这其实是一个挺典型的场景:一个旨在展示或教授某种技能的仓库,本身却成了安…

2026/7/9 5:56:19阅读更多 →
MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

# MLT 2026启示:因果推理与概率建模驱动下一代LLM应用## 一、背景与挑战:从“黑箱预测”到“可信推理”2026年6月,第7届机器学习与趋势国际会议(MLT 2026)将在悉尼召开。会议议程中,“因果与可解释机器学习…

2026/7/8 7:00:12阅读更多 →
通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

1. 项目概述与漏洞背景最近在梳理一些历史OA系统的安全风险时,通达OA v11.6版本中的一个老漏洞又进入了我的视线。这个漏洞位于/general/bi_design/appcenter/report_bi.func.php文件中,是一个典型的SQL注入点。虽然这个漏洞的利用方式看起来并不复杂&am…

2026/7/9 2:47:07阅读更多 →
Three.js 着色器光效教程

Three.js 着色器光效教程

着色器光效 Shader Light ▶ 在线运行案例 案例合集: 三维可视化功能案例(threehub.cn)开源仓库github地址: https://github.com/z2586300277/three-cesium-examples400个案例代码: 网盘链接 你将学到什么 ShaderMaterial 自…

2026/7/9 0:04:37阅读更多 →
如何5分钟掌握CS2智能库存管理:开源工具CASEMOVE终极指南

如何5分钟掌握CS2智能库存管理:开源工具CASEMOVE终极指南

如何5分钟掌握CS2智能库存管理:开源工具CASEMOVE终极指南 【免费下载链接】casemove A dedicated desktop app that enables you to move items in and out of storage units in CS2. 项目地址: https://gitcode.com/gh_mirrors/ca/casemove 还在为CS2存储单…

2026/7/9 0:04:37阅读更多 →
GBase 8a vs MySQL 8.0:ALTER TABLE语法与限制的5点关键差异对比

GBase 8a vs MySQL 8.0:ALTER TABLE语法与限制的5点关键差异对比

GBase 8a与MySQL 8.0:ALTER TABLE语法差异深度解析与实战指南1. 两种数据库的ALTER TABLE能力全景对比在数据库架构设计和运维过程中,表结构变更(DDL操作)是不可避免的需求。GBase 8a作为国产分析型数据库代表,与开源M…

2026/7/9 0:04:37阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/8 6:59:54阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/8 13:42:39阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/8 13:42:39阅读更多 →