Java动态代理入门:在运行时凭空捏造一个中介

发布时间:2026/7/19 3:21:57
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 框架设计的核心套路。