28_Java动态代理详解
2026/6/11 4:38:57 网站建设 项目流程

Java动态代理详解

文章目录

  • Java动态代理详解
    • 前言
    • 一、为什么需要代理模式
    • 二、JDK动态代理核心API
      • 2.1 InvocationHandler 接口
      • 2.2 Proxy 类
    • 三、第一个JDK动态代理示例
    • 四、理解JDK动态代理的原理
    • 五、动态代理的高级用法
      • 5.1 通用的代理工厂
      • 5.2 方法级别的过滤
      • 5.3 多个InvocationHandler的链式调用
    • 六、动态代理在Spring AOP中的应用
    • 七、JDK动态代理 vs CGLIB动态代理
    • 总结
    • ✅ 亮点总结
    • 适用场景
    • 扩展方向

前言

**动态代理(Dynamic Proxy)**是Java中一种在运行时动态创建代理对象的技术,它是AOP(面向切面编程)的核心实现机制。Spring AOP、MyBatis Mapper接口实现、RPC框架的远程调用等,背后都离不开动态代理的支持。

动态代理技术将前面学过的"反射"推向了实用化的高峰。如果说反射是"在运行时访问类的能力",动态代理就是"在运行时创建新类的能力"——它结合了反射和时间代理模式,让你可以在不修改原始类的前提下,为其所有方法统一添加日志、事务、权限等横切逻辑。在面试中,动态代理是衡量候选人Java高级特性掌握程度的试金石:能否解释清楚JDK动态代理与CGLIB的区别?能否手写一个简单的AOP实现?都建立在对动态代理的深入理解之上。Java提供了两种动态代理方式:JDK动态代理(基于接口)和CGLIB动态代理(基于继承)。本文将重点讲解JDK动态代理的原理和实战应用。

一、为什么需要代理模式

代理模式是一种结构型设计模式,通过代理对象控制对原始对象的访问。它的核心思想是"间接访问"——不直接调用目标对象,而是通过一个代理来间接调用,在调用前后可以插入额外的逻辑。

在Java中,代理模式从"静态代理"演进到"动态代理",解决了一个核心痛点:代码重复。如果一个系统有100个Service类,每个都需要加上日志和事务,静态代理意味着你需要手写100个代理类;而动态代理只需要一个InvocationHandler,就能代理所有接口。这也是为什么Spring选择动态代理作为AOP的底层实现。

常见场景:

  • 日志记录:方法调用前后自动打印日志
  • 性能监控:统计方法执行时间
  • 事务管理:自动开启和提交/回滚事务
  • 权限校验:调用前检查用户权限
  • 缓存处理:方法结果缓存,减少重复计算
// 没有代理:日志代码和业务代码混在一起publicvoidsaveUser(Useruser){System.out.println("【日志】开始执行 saveUser");longstart=System.currentTimeMillis();// 业务逻辑database.insert(user);longend=System.currentTimeMillis();System.out.println("【日志】saveUser 执行完成,耗时 "+(end-start)+"ms");}// 每个方法都要写重复的日志代码 → 难以维护!

使用代理后,日志代码与业务代码分离,实现关注点分离

二、JDK动态代理核心API

JDK动态代理主要涉及两个核心类:

2.1 InvocationHandler 接口

packagejava.lang.reflect;publicinterfaceInvocationHandler{/** * @param proxy 代理对象 * @param method 被调用的方法 * @param args 方法参数 * @return 方法返回值 */Objectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable;}

所有对代理对象的方法调用都会被转发到这个invoke方法中处理。

2.2 Proxy 类

// 创建代理对象的静态方法publicstaticObjectnewProxyInstance(ClassLoaderloader,// 类加载器Class<?>[]interfaces,// 要代理的接口列表InvocationHandlerh// 调用处理器)

三、第一个JDK动态代理示例

importjava.lang.reflect.*;// 1. 定义接口interfaceUserService{voidaddUser(Stringname);voiddeleteUser(intid);StringgetUser(intid);}// 2. 实现类(被代理对象)classUserServiceImplimplementsUserService{@OverridepublicvoidaddUser(Stringname){System.out.println("【业务】添加用户:"+name);}@OverridepublicvoiddeleteUser(intid){System.out.println("【业务】删除用户,ID:"+id);}@OverridepublicStringgetUser(intid){System.out.println("【业务】查询用户,ID:"+id);return"User{id="+id+", name='张三'}";}}// 3. InvocationHandler实现(日志增强)classLogHandlerimplementsInvocationHandler{privateObjecttarget;// 被代理的目标对象publicLogHandler(Objecttarget){this.target=target;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{// 前置增强System.out.println("【日志】开始执行 "+method.getName()+"()");longstart=System.currentTimeMillis();try{// 调用目标方法Objectresult=method.invoke(target,args);returnresult;}finally{// 后置增强longend=System.currentTimeMillis();System.out.println("【日志】"+method.getName()+"() 执行完成,耗时 "+(end-start)+"ms");}}}// 4. 测试publicclassProxyDemo1{publicstaticvoidmain(String[]args){// 创建目标对象UserServicetarget=newUserServiceImpl();// 创建代理对象UserServiceproxy=(UserService)Proxy.newProxyInstance(target.getClass().getClassLoader(),// 类加载器target.getClass().getInterfaces(),// 实现的所有接口newLogHandler(target)// InvocationHandler);// 通过代理调用方法proxy.addUser("张三");System.out.println("---");Stringuser=proxy.getUser(1);System.out.println("返回结果:"+user);System.out.println("---");proxy.deleteUser(2);// 查看代理类的类型System.out.println("\n代理类名:"+proxy.getClass().getName());// 输出类似:com.sun.proxy.$Proxy0}}

运行输出

【日志】开始执行 addUser() 【业务】添加用户:张三 【日志】addUser() 执行完成,耗时 0ms --- 【日志】开始执行 getUser() 【业务】查询用户,ID:1 【日志】getUser() 执行完成,耗时 0ms 返回结果:User{id=1, name='张三'} --- 【日志】开始执行 deleteUser() 【业务】删除用户,ID:2 【日志】deleteUser() 执行完成,耗时 0ms 代理类名:com.sun.proxy.$Proxy0

四、理解JDK动态代理的原理

JDK动态代理在运行时动态生成一个实现了指定接口的代理类(如$Proxy0),这个类的方法实现如下:

// JDK动态生成的代理类简化示意publicfinalclass$Proxy0extendsProxyimplementsUserService{privatestaticMethodm3;// addUserprivatestaticMethodm4;// deleteUserprivatestaticMethodm5;// getUserpublic$Proxy0(InvocationHandlerh){super(h);}@OverridepublicvoidaddUser(Stringname){// 调用h.invoke(),将方法调用转发给InvocationHandlerthis.h.invoke(this,m3,newObject[]{name});}@OverridepublicvoiddeleteUser(intid){this.h.invoke(this,m4,newObject[]{id});}@OverridepublicStringgetUser(intid){return(String)this.h.invoke(this,m5,newObject[]{id});}}

核心原理:生成的代理类继承了java.lang.reflect.Proxy,实现了指定的接口,并将每个方法调用转发给InvocationHandler.invoke()

JDK动态代理的限制:只能代理实现了接口的类。如果目标类没有实现任何接口,则无法使用JDK动态代理(需要使用CGLIB)。

五、动态代理的高级用法

5.1 通用的代理工厂

importjava.lang.reflect.*;classProxyFactory{/** * 创建动态代理对象的通用方法 * @param target 目标对象 * @param handler 自定义InvocationHandler * @return 代理对象 */@SuppressWarnings("unchecked")publicstatic<T>TcreateProxy(Ttarget,InvocationHandlerhandler){return(T)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);}}

5.2 方法级别的过滤

有时我们需要对不同方法做不同处理:

importjava.lang.reflect.*;importjava.util.*;classSelectiveHandlerimplementsInvocationHandler{privateObjecttarget;privateSet<String>noLogMethods=newHashSet<>();publicSelectiveHandler(Objecttarget){this.target=target;// 不需要日志的方法(如toString、hashCode等)noLogMethods.add("toString");noLogMethods.add("hashCode");noLogMethods.add("equals");}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{StringmethodName=method.getName();// 过滤不需要增强的方法if(noLogMethods.contains(methodName)){returnmethod.invoke(target,args);}System.out.println("[AOP] before: "+methodName);Objectresult=method.invoke(target,args);System.out.println("[AOP] after: "+methodName);returnresult;}}

5.3 多个InvocationHandler的链式调用

importjava.lang.reflect.*;importjava.util.*;classChainableProxy{@SuppressWarnings("unchecked")publicstatic<T>Twrap(Ttarget,InvocationHandler...handlers){Objectcurrent=target;// 从最后一个handler开始包裹(实现链式调用)for(inti=handlers.length-1;i>=0;i--){InvocationHandlerhandler=handlers[i];ObjectfinalCurrent=current;current=Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),(proxy,method,args)->{// 判断当前handler是否需要处理returnhandler.invoke(finalCurrent,method,args);});}return(T)current;}}// 日志处理器classLoggingHandlerimplementsInvocationHandler{privateObjecttarget;publicLoggingHandler(Objecttarget){this.target=target;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{System.out.println("[日志] 调用 "+method.getName());returnmethod.invoke(target,args);}}// 权限校验处理器classAuthHandlerimplementsInvocationHandler{privateObjecttarget;publicAuthHandler(Objecttarget){this.target=target;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{System.out.println("[权限] 校验权限通过");returnmethod.invoke(target,args);}}

六、动态代理在Spring AOP中的应用

Spring AOP底层就使用了动态代理。理解这一点非常关键——当你在Spring中使用@Transactional@Cacheable注解时,Spring实际上为你创建了一个"看不见的代理对象"。你拿到的userService对象已经不是最初的UserServiceImpl实例了,而是一个动态生成的代理对象,它在调用你的方法前后织入了事务管理或缓存逻辑。

以下模拟Spring AOP的基本原理:

importjava.lang.reflect.*;importjava.util.*;// ========== AOP术语模拟 ==========// 切面classAspect{publicvoidbefore(StringmethodName){System.out.println("[AOP-Before] 前置通知: "+methodName);}publicvoidafter(StringmethodName){System.out.println("[AOP-After] 后置通知: "+methodName);}publicvoidafterReturning(StringmethodName,Objectresult){System.out.println("[AOP-AfterReturning] 返回通知: "+methodName+" -> "+result);}publicvoidafterThrowing(StringmethodName,Exceptione){System.out.println("[AOP-AfterThrowing] 异常通知: "+methodName+" -> "+e.getMessage());}}// AOP代理创建器classAopProxy{@SuppressWarnings("unchecked")publicstatic<T>TcreateProxy(Ttarget,Aspectaspect){return(T)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),(proxy,method,args)->{StringmethodName=method.getName();try{aspect.before(methodName);Objectresult=method.invoke(target,args);aspect.afterReturning(methodName,result);aspect.after(methodName);returnresult;}catch(InvocationTargetExceptione){aspect.afterThrowing(methodName,(Exception)e.getCause());aspect.after(methodName);throwe.getCause();}});}}// 业务接口interfaceOrderService{voidcreateOrder(Stringproduct,intqty);intgetOrderCount();}classOrderServiceImplimplementsOrderService{@OverridepublicvoidcreateOrder(Stringproduct,intqty){System.out.println("【核心业务】创建订单:"+product+" x "+qty);}@OverridepublicintgetOrderCount(){System.out.println("【核心业务】查询订单数量");return100;}}// 测试publicclassAopDemo{publicstaticvoidmain(String[]args){OrderServicetarget=newOrderServiceImpl();Aspectaspect=newAspect();OrderServiceproxy=AopProxy.createProxy(target,aspect);proxy.createOrder("iPhone 15",1);System.out.println("==========");intcount=proxy.getOrderCount();System.out.println("订单数量:"+count);}}

运行输出

[AOP-Before] 前置通知: createOrder 【核心业务】创建订单:iPhone 15 x 1 [AOP-AfterReturning] 返回通知: createOrder -> null [AOP-After] 后置通知: createOrder ========== [AOP-Before] 前置通知: getOrderCount 【核心业务】查询订单数量 [AOP-AfterReturning] 返回通知: getOrderCount -> 100 [AOP-After] 后置通知: getOrderCount 订单数量:100

七、JDK动态代理 vs CGLIB动态代理

特性JDK动态代理CGLIB动态代理
实现机制反射 + 接口字节码增强(继承)
代理对象要求必须实现接口不需要接口(不能是final类)
性能生成快,调用略慢(反射)生成慢,调用快(直接调用)
依赖JDK内置需要第三方库
Spring中的选择优先使用(有接口时)无接口时的备选

Spring AOP的策略:如果目标对象实现了接口,默认使用JDK动态代理;如果没有实现接口,使用CGLIB。Spring Boot 2.x开始默认使用CGLIB。

为什么Spring Boot 2.x默认改用CGLIB?主要原因有三:一是CGLIB不再需要目标类实现接口,使用更灵活;二是CGLIB是基于继承的代理,调用时不需要反射(直接调用父类方法),性能更好;三是JDK动态代理返回的是接口类型,类型转换时容易出错。但CGLIB也有自己的限制:不能代理final类或final方法。

// Spring中设置代理方式// application.properties// spring.aop.proxy-target-class=true // 强制使用CGLIB// spring.aop.proxy-target-class=false // 接口时使用JDK代理

总结

本文详细讲解了Java动态代理的核心知识:

  • 代理模式:将横切关注点(日志、事务、权限等)从业务代码中分离
  • InvocationHandler:所有代理方法调用都转发到此接口的invoke方法
  • Proxy.newProxyInstance():在运行时动态创建代理对象,需要接口和InvocationHandler
  • 实现原理:生成一个继承Proxy并实现指定接口的$Proxy0
  • AOP模拟:完整展示了前置通知、后置通知、返回通知和异常通知的实现
  • JDK vs CGLIB:JDK基于接口、内置、反射调用;CGLIB基于继承、字节码增强、直接调用

动态代理是Spring框架的核心技术基础,深刻理解它的原理有助于更好地使用Spring AOP、事务管理等高级特性。

✅ 亮点总结

  • InvocationHandler+Proxy.newProxyInstance()两步完成动态代理创建,简洁而强大
  • 模拟AOP完整实现了前置通知、后置通知、返回通知、异常通知,直击Spring AOP内核
  • JDK动态代理 vs CGLIB的详细对比(接口 vs 继承、反射调用 vs 字节码直接调用),覆盖了面试高频考点
  • $Proxy0类结构解析揭开了动态代理的内部实现面纱
  • Spring AOP代理选择策略(有接口用JDK,无接口用CGLIB)与proxy-target-class配置的深入解读

适用场景

  • 为Service层统一添加日志、事务管理、权限校验等横切关注点
  • MyBatis中只定义接口就能执行SQL——背后正是动态代理将Mapper接口转为代理对象
  • RPC框架(如Dubbo、Feign)中,通过动态代理将远程调用伪装成本地方法调用

扩展方向

  • CGLIB深入:了解EnhancerMethodInterceptorCallbackFilter等CGLIB核心API
  • Spring AOP源码分析:跟踪JdkDynamicAopProxyCglibAopProxy的源码实现(推荐阅读下一篇:Java Lambda表达式入门)
  • 字节码框架ASM:直接操作字节码,实现比CGLIB更底层、更灵活的代理需求

上一篇:Java注解Annotation详解 | 下一篇:[Java Lambda表达式入门](29_Java Lambda表达式入门.md)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询