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/19 3:22:04 阅读更多 →
【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/19 3:22:04 阅读更多 →
机器学习模型生产化:从部署到高可用的四大工程支柱

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

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

2026/7/19 3:21:04 阅读更多 →

最新新闻

美的BD/BC-143KMF(E)冰柜评测:一级能效如何实现长期省电省钱

美的BD/BC-143KMF(E)冰柜评测:一级能效如何实现长期省电省钱

最近在帮朋友选购家用冰柜时&#xff0c;发现一个很有意思的现象&#xff1a;很多人在选购时只关注容量和价格&#xff0c;却忽略了一个更重要的指标——长期使用成本。一台看似便宜的冰柜&#xff0c;如果耗电量大&#xff0c;几年下来可能比购买时省下的钱还要多。今天我们就…

2026/7/19 5:31:16 阅读更多 →
如何基于Dify快速构建AI原生应用

如何基于Dify快速构建AI原生应用

部署效果 Dify入门 官网&#xff1a;https://cloud.dify.ai/apps Dify 是一款开源的大语言模型(LLM) 应用开发平台。它融合了后端即服务&#xff08;Backend as Service&#xff09;和 LLMOps 的理念&#xff0c;使开发者可以快速搭建生产级的生成式 AI 应用。即使你是非技术…

2026/7/19 5:31:16 阅读更多 →
AWS EC2上部署机器学习模型:从Flask API到生产级MaaS服务

AWS EC2上部署机器学习模型:从Flask API到生产级MaaS服务

1. 项目概述&#xff1a;把训练好的模型变成随时可调用的“网络接口”&#xff0c;而不是藏在本地电脑里的文件你辛辛苦苦调了两周超参&#xff0c;终于让模型在验证集上准确率冲到了92.7%&#xff0c;心里刚松一口气&#xff0c;产品同学发来消息&#xff1a;“模型什么时候能…

2026/7/19 5:31:16 阅读更多 →
三柱式过电压保护器C相接地测试原理与现场操作指南

三柱式过电压保护器C相接地测试原理与现场操作指南

在电力系统运维和电气设备预防性试验中&#xff0c;过电压保护器的性能测试是保障电网安全稳定运行的关键环节。三柱式过电压保护器因其结构特点&#xff0c;在测试时需要特别注意相间及相对地的绝缘配合特性。本文将以C相与地相之间的测试为切入点&#xff0c;详细讲解测试原理…

2026/7/19 5:31:16 阅读更多 →
USBCopyer:Windows平台智能USB文件自动备份工具的完整使用指南

USBCopyer:Windows平台智能USB文件自动备份工具的完整使用指南

USBCopyer&#xff1a;Windows平台智能USB文件自动备份工具的完整使用指南 【免费下载链接】USBCopyer &#x1f609; 用于在插上U盘后自动按需复制该U盘的文件。”备份&偷U盘文件的神器”&#xff08;写作USBCopyer&#xff0c;读作USBCopier&#xff09; 项目地址: htt…

2026/7/19 5:31:16 阅读更多 →
MCP协议:AI Agent工具协作的轻量级语义契约

MCP协议:AI Agent工具协作的轻量级语义契约

1. 项目概述&#xff1a;MCP不是新协议&#xff0c;而是AI Agent协作的“操作系统内核”“MCP is Taking Over”这个标题乍看像科技媒体惯用的夸张修辞&#xff0c;但如果你最近翻过LangChain、LlamaIndex的GitHub Issues&#xff0c;或者在Hugging Face的Agent Bench排行榜上留…

2026/7/19 5:30:15 阅读更多 →

日新闻

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

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

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

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

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

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

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

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

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

2026/7/19 0:00:40 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/7/19 0:00:40 阅读更多 →

月新闻