从零开始理解 JDK 动态代理保姆级教程1. 生活中的“代理”——先理解代理模式你有没有找过明星合影通常你没法直接联系到明星本人而是通过经纪人。你把自己的请求合影告诉经纪人经纪人判断是否能打扰明星然后再转达给明星最后由明星完成合影经纪人可能还会加一句“注意形象保持微笑”。在这个场景里你— 调用方客户端经纪人— 代理明星— 真实对象目标对象代理的作用就是在访问真实对象之前或之后添加一些额外的操作比如过滤请求、记录日志、控制权限但最终还是要调用真实对象的方法。2. 静态代理每个经纪人只能服务一个明星在编程里最原始的代理做法是静态代理为每一个需要被代理的类都单独手写一个代理类。比如有一个接口StarpublicinterfaceStar{voidsing();}真实明星类publicclassRealStarimplementsStar{Overridepublicvoidsing(){System.out.println(明星本人在唱歌);}}静态代理类publicclassStarProxyimplementsStar{privateStarrealStar;// 持有真实明星publicStarProxy(StarrealStar){this.realStarrealStar;}Overridepublicvoidsing(){System.out.println(经纪人确认场地收钱);realStar.sing();// 调真实明星唱歌System.out.println(经纪人安排粉丝见面会);}}使用StarrealStarnewRealStar();StarproxynewStarProxy(realStar);proxy.sing();这样做有什么问题假设你又有Actor​、Dancer​ 等接口每个都要写一个对应的代理类代码会无限膨胀无法复用。有没有一种方法能自动生成代理类不用手工为每个接口重复写逻辑呢——于是动态代理诞生了。3. JDK 动态代理是什么JDK 动态代理是 Java 原生提供的一种机制可以在程序运行时而不是编译时动态地创建出一个代理对象这个对象会实现你指定的接口并将所有方法调用转发到一个统一的处理器上。它的核心组件只有两个​​java.lang.reflect.Proxy​用来生成代理对象的工厂类。​​java.lang.reflect.InvocationHandler​​接口里面只有一个方法invoke你需要在这里写好「拦截逻辑」也就是代理要做的那些额外事情。关键约束JDK 动态代理只能代理实现了接口的类无法直接代理一个没有实现任何接口的普通类。如果你需要代理普通类可以用 CGLIB 这类第三方库不是本篇重点最后会简单对比4. 手把手写一个 JDK 动态代理我们还是用明星的例子但这次用一个通用的日志记录代理任何接口都能复用。4.1 定义接口和实现类// 明星接口publicinterfaceStar{voidsing(StringsongName);Stringdance();}// 真实明星只会唱歌跳舞publicclassRealStarimplementsStar{Overridepublicvoidsing(StringsongName){System.out.println(本明星唱了一首songName);}OverridepublicStringdance(){System.out.println(本明星跳了一支热舞);return街舞;}}4.2 编写InvocationHandler—— 代理的“大脑”这个处理器就是你的经纪人逻辑。它需要实现InvocationHandler​ 接口并实现invoke方法importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;publicclassLogHandlerimplementsInvocationHandler{privateObjecttarget;publicLogHandler(Objecttarget){this.targettarget;}OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{System.out.println(【日志】准备调用方法method.getName());if(args!nullargs.length0){System.out.println(【日志】参数java.util.Arrays.toString(args));}Objectresultmethod.invoke(target,args);System.out.println(【日志】方法调用完毕返回值result);returnresult;}}4.3 使用Proxy.newProxyInstance创建代理对象importjava.lang.reflect.Proxy;publicclassMain{publicstaticvoidmain(String[]args){StarrealStarnewRealStar();LogHandlerhandlernewLogHandler(realStar);StarproxyStar(Star)Proxy.newProxyInstance(realStar.getClass().getClassLoader(),newClass[]{Star.class},handler);proxyStar.sing(夜曲);System.out.println(----------);StringresultproxyStar.dance();System.out.println(最终拿到的返回值result);}}运行结果【日志】准备调用方法sing 【日志】参数[夜曲] 本明星唱了一首夜曲 【日志】方法调用完毕返回值null ---------- 【日志】准备调用方法dance 本明星跳了一支热舞 【日志】方法调用完毕返回值街舞 最终拿到的返回值街舞5. 原理探秘生成的代理类长什么样JDK 动态代理在运行时会生成一个形如$Proxy0的类类似这样伪代码publicfinalclass$Proxy0extendsProxyimplementsStar{public$Proxy0(InvocationHandlerh){super(h);}Overridepublicvoidsing(StringsongName){super.h.invoke(this,m3,newObject[]{songName});}OverridepublicStringdance(){return(String)super.h.invoke(this,m4,null);}}由于 Java 是单继承代理类已经继承了Proxy所以只能实现接口——这就是 JDK 动态代理必须基于接口的原因。6. JDK 动态代理的常见应用场景6.1 AOP面向切面编程比如你想给一个项目中所有 Service 的方法都加上事务管理、日志或耗时统计。不用挨个改类用动态代理一包裹就行。 Spring AOP 实战给 Service 层添加日志新建 Spring Boot 项目引入spring-boot-starter-aop。定义接口和实现类// UserService.javapublicinterfaceUserService{voidlogin(Stringusername);StringgetUserInfo(Longid);}// UserServiceImpl.javaimportorg.springframework.stereotype.Service;ServicepublicclassUserServiceImplimplementsUserService{Overridepublicvoidlogin(Stringusername){System.out.println(username 登录成功);}OverridepublicStringgetUserInfo(Longid){System.out.println(查询用户 id id);return用户详细信息;}}编写切面AspectComponentpublicclassLogAspect{Around(execution(* com.example.demo.service.UserService.*(..)))publicObjectaround(ProceedingJoinPointjoinPoint)throwsThrowable{StringmethodNamejoinPoint.getSignature().getName();Object[]argsjoinPoint.getArgs();System.out.println(【Spring日志】调用方法methodName);ObjectresultjoinPoint.proceed();System.out.println(【Spring日志】方法执行完毕返回值result);returnresult;}}启动类测试SpringBootApplicationpublicclassDemoApplication{publicstaticvoidmain(String[]args){ConfigurableApplicationContextcontextSpringApplication.run(DemoApplication.class,args);UserServiceuserServicecontext.getBean(UserService.class);userService.login(小白);StringinfouserService.getUserInfo(1001L);System.out.println(最终获得的用户信息info);System.out.println(代理对象类型userService.getClass().getName());}}输出【Spring日志】调用方法login 小白 登录成功 【Spring日志】方法执行完毕返回值null 【Spring日志】调用方法getUserInfo 查询用户 id 1001 【Spring日志】方法执行完毕返回值用户详细信息 代理对象类型com.sun.proxy.$Proxy50可以看到Spring 容器里的UserService​ bean 实际是一个 JDK 动态代理对象。那么问题来了Spring 具体是怎么生成这个代理对象的 Spring 底层是如何创建 JDK 动态代理的源码浅析Spring AOP 的代理生成过程可以拆成这几个步骤我用类比来理解1. Spring 的“经纪人中介”——​​DefaultAopProxyFactory​在 Spring 内部有一个专门“决定用哪种代理方式”的工厂叫DefaultAopProxyFactory​。它的主要方法是createAopProxy(AdvisedSupport config)​。它会根据被代理的目标对象是否实现了接口来做判断如果目标对象实现了至少一个接口 → 创建JdkDynamicAopProxyJDK 动态代理否则 → 创建CglibAopProxyCGLIB 代理这就像一个中介看你有没有“接口营业执照”——有的话走 JDK 通道没有就走 CGLIB 通道。2. 代理的“实际执行者”——​​JdkDynamicAopProxy​​JdkDynamicAopProxy​ 这个类同时实现了AopProxy​ 接口和InvocationHandler​ 接口。它是 Spring 用作 JDK 动态代理的关键。它内部有一个getProxy()​ 方法就是在这个方法里调用了熟悉的Proxy.newProxyInstance。简化后的关键源码长这样publicObjectgetProxy(NullableClassLoaderclassLoader){// 获取目标对象实现的接口Class?[]proxiedInterfacesAopProxyUtils.completeProxiedInterfaces(this.advised,true);// 调用 Java 原生方法创建代理对象this 就是 InvocationHandlerreturnProxy.newProxyInstance(classLoader,proxiedInterfaces,this);}这和我们自己手写LogHandler​ 时的做法如出一辙只不过JdkDynamicAopProxy​ 的invoke方法里会执行一系列“通知”前置通知、后置通知、环绕通知等而不仅仅是简单地打日志。3. 代理创建的“触发时机”——​​AbstractAutoProxyCreator​Spring 怎么知道哪些 Bean 需要被代理在 Bean 的生命周期中有一个特殊的BeanPostProcessor​ 叫做AbstractAutoProxyCreator​具体子类如AnnotationAwareAspectJAutoProxyCreator​。它在 Bean 初始化之后会调用wrapIfNecessary()​ 方法检查这个 Bean 是否需要被增强比如有没有匹配的切面。如果需要就会使用上述的DefaultAopProxyFactory来创建代理对象并返回代理 bean从而替换掉原始对象。流程可以总结为Spring 启动扫描所有切面。创建UserServiceImpl的 Bean。​BeanPostProcessor发现该 Bean 有匹配的切面决定要为它生成代理。​DefaultAopProxyFactory.createAopProxy()​ 判断UserServiceImpl​ 实现了UserService​ 接口 → 选用JdkDynamicAopProxy。​JdkDynamicAopProxy.getProxy()​ 调用Proxy.newProxyInstance​生成$Proxy类实例并注入所有拦截器链。最终容器存放的是这个代理对象而不是原始的UserServiceImpl。所以你在测试代码里通过context.getBean(UserService.class)​ 拿到的正是这个自动生成的代理对象它实现了UserService​ 接口内部通过JdkDynamicAopProxy​ 的invoke方法调度所有切面逻辑和真实方法。6.2 RPC 框架远程过程调用在 Dubbo 等框架中客户端只拿到一个接口通过动态代理生成的代理对象调用任意方法时实际是发送网络请求到远程服务器并返回结果。你完全感受不到网络的存在。6.3 MyBatis 的核心实现MyBatis 我们只写 Mapper 接口不写实现类为什么能执行 SQL实际上 MyBatis 用 JDK 动态代理为每个 Mapper 接口生成了代理对象当你调用mapper.findById(1)​ 时代理的invoke方法会根据接口全限定名和方法名找到对应的 SQL 并执行。6.4 各种拦截器、权限控制可以在invoke方法里面先判断是否有权限再决定是否执行真实方法。7. 总结与注意事项✅优点无需手动编写代理类复用便捷将横切逻辑日志、事务与业务逻辑解耦Java 原生支持零依赖Spring AOP 默认借助它优雅地实现了声明式切面⚠️局限只能代理接口被代理的类必须至少实现一个接口如果代理没有接口的普通类Spring 会自动转用 CGLIB但原生 JDK 方式会报错代理内部通过反射调用有一定性能开销现代 JVM 优化后影响很小延伸对比CGLIBCGLIB 通过继承目标类来生成子类代理可代理无接口的类。不能代理final​ 类或final方法。Spring AOP 默认优先 JDK 动态代理无接口时切换 CGLIB。8. 动手做一做建议亲自实践把例子中UserServiceImpl​ 的接口去掉看控制台代理类名是否变成 CGLIB 的$$EnhancerBySpringCGLIB。给原生LogHandler增加计时功能。查源码阅读JdkDynamicAopProxy​ 的invoke方法看它如何执行通知链。希望这篇“大白话”教程让你对 JDK 动态代理以及它在 Spring 中的工作原理有了透彻的理解。当你看懂 Spring AOP 或 MyBatis 源码的那天一定会想起今天这个“经纪人”和“经纪人中介”的故事。