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/7/21 20:16:14 阅读更多 →
【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/7/23 15:27:35 阅读更多 →
机器学习模型生产化:从部署到高可用的四大工程支柱

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

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

2026/7/21 20:16:15 阅读更多 →

最新新闻

Codex 修改登录权限总出问题?先梳理认证链路再动代码

Codex 修改登录权限总出问题?先梳理认证链路再动代码

摘要登录成功却不断跳回登录页、刷新后权限丢失、普通用户看到管理员菜单&#xff0c;都是前端项目中常见的认证问题。这类故障通常同时涉及 Token、用户状态、路由守卫和接口权限。本文介绍如何让 Codex 先梳理完整认证链路&#xff0c;再进行最小范围修改和回归验证。很多开发…

2026/7/23 15:27:17 阅读更多 →
Claude Code环境配置与依赖冲突导致的信用额度报错排查指南

Claude Code环境配置与依赖冲突导致的信用额度报错排查指南

最近在尝试用 Claude Code 重构一个老项目时&#xff0c;遇到了一个让人头疼的问题&#xff1a;明明本地测试一切正常&#xff0c;但一提交到生产环境就提示“信用额度不足”。起初以为是 API 调用超限&#xff0c;但检查日志发现请求频率完全在合理范围内。这个问题困扰了我整…

2026/7/23 15:27:17 阅读更多 →
荣颖电子-RYOP184/RYOP284 零漂移运放

荣颖电子-RYOP184/RYOP284 零漂移运放

2026/7/23 15:27:17 阅读更多 →
Unity手游热更新实战:ToLua集成、资源加密与版本管理全解析

Unity手游热更新实战:ToLua集成、资源加密与版本管理全解析

1. 项目概述&#xff1a;为什么Unity热更新是移动游戏开发的“生命线”&#xff1f;在移动游戏这个竞争白热化的领域&#xff0c;上线只是起点&#xff0c;持续的运营和内容迭代才是决定产品成败的关键。想象一下&#xff0c;你的游戏刚上线&#xff0c;玩家反馈了一个致命的Bu…

2026/7/23 15:27:17 阅读更多 →
新一代仪器化落锤冲击试验机如何重塑材料抗冲击性能评估标准

新一代仪器化落锤冲击试验机如何重塑材料抗冲击性能评估标准

引言在航空航天、新能源汽车、高端建材等先进制造领域&#xff0c;材料的抗冲击性能是决定产品可靠性与安全性的关键指标。传统的落锤冲击测试方法&#xff0c;长期受困于能量控制不准、数据采集不全、操作效率低下及安全防护不足等痛点&#xff0c;导致研发与质控数据缺乏公信…

2026/7/23 15:27:17 阅读更多 →
信号链设计实战:从放大器选型到系统集成,工程师避坑指南

信号链设计实战:从放大器选型到系统集成,工程师避坑指南

1. 信号链&#xff1a;从物理世界到数字世界的桥梁在任何一个需要感知、测量或控制物理世界的电子系统中&#xff0c;信号链都是其最核心的“神经系统”。无论是工业自动化产线上检测零件尺寸的传感器&#xff0c;还是医疗设备中监测生命体征的探头&#xff0c;亦或是智能手机里…

2026/7/23 15:26:17 阅读更多 →

日新闻

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;从单点好评到指数级传播&#xff1a;AI副业主理人必须掌握的4层口碑渗透模型&#xff08;含ROI测算表&#xff09; 当AI副业主理人不再仅满足于单次服务交付&#xff0c;而是主动构建可复用、可裂变、可…

2026/7/23 0:00:25 阅读更多 →
AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析

AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析

更多请点击&#xff1a; https://codechina.net 第一章&#xff1a;AI写作开头钩子设计&#xff1a;为什么你的AI文案完读率不足18%&#xff1f;——基于2,346篇A/B测试报告的归因分析 在对2,346篇跨行业AI生成文案的A/B测试数据进行聚类分析后&#xff0c;我们发现&#xff1…

2026/7/23 0:01:26 阅读更多 →
Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南&#xff1a;免费开源的终极点对点安全聊天工具 【免费下载链接】chitchatter Secure peer-to-peer chat that is serverless, decentralized, and ephemeral 项目地址: https://gitcode.com/gh_mirrors/ch/chitchatter Chitchatter是一款革命性的安…

2026/7/23 0:01:26 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中&#xff0c;我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源&#xff0c;还是配置文件、证书等&#xff0c;都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下&#xff0c;但这…

2026/7/22 8:58:19 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP&#xff08;轻量级目录访问协议&#xff09;作为企业级身份认证的黄金标准&#xff0c;已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时&#xff0c;发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/22 19:43:43 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”&#xff0c;而是以可解释、可审计、可迭代的方式&#xff0c;赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/22 12:54:44 阅读更多 →

月新闻