2026年4月8日首发 · 技术入门/进阶学习者、在校学生、面试备考者、相关技术栈开发工程师必读
你是否遇到过这样的场景:想给Service层的方法加日志打印,却不得不在每个方法里重复写log.info(...);想给支付接口加权限校验,又担心修改原有逻辑会引入bug;想给旧系统加性能监控,却不敢动老代码——这些“在不入侵原有代码的前提下给业务逻辑附加额外功能”的需求,几乎每个Java开发者都会碰到。而代理模式,正是解决这些问题的经典设计模式。

动态代理是Java核心技术栈中的重要知识点,在Spring AOP、MyBatis插件、Dubbo过滤器等主流框架底层都扮演着关键角色。本文将从痛点出发,带你从静态代理一路走到JDK动态代理和CGLIB动态代理,涵盖原理讲解、代码示例和面试要点,建立完整的知识链路。
一、痛点切入:为什么需要代理模式

1.1 传统做法的代码
假设我们要给用户服务类添加日志功能,最常见的做法是直接在业务方法里写日志:
// 目标类:业务逻辑 + 日志代码混在一起 public class UserServiceImpl { public void addUser(String username) { System.out.println("〖日志〗开始执行addUser,参数:" + username); // 日志代码 System.out.println("数据库新增用户:" + username); // 业务代码 System.out.println("〖日志〗addUser执行完成"); // 日志代码 } public void deleteUser(String username) { System.out.println("〖日志〗开始执行deleteUser,参数:" + username); System.out.println("数据库删除用户:" + username); System.out.println("〖日志〗deleteUser执行完成"); } }
1.2 这种做法的缺点
代码冗余:每个方法都要重复写日志代码,违反了DRY(Don‘t Repeat Yourself)原则-5;
耦合度高:日志、权限、事务等横切关注点与业务逻辑强行绑定,破坏单一职责原则-5;
维护成本高:日志格式需要变更时,要修改所有包含日志代码的类,效率低下且容易出错-5;
可扩展性差:需要为每个业务方法手写增强逻辑,新增接口就要新增代码。
1.3 代理模式的解决方案
代理模式正是为解决这一矛盾而生的设计模式。它引入一个“中间人”——代理对象,在调用目标对象之前/之后插入附加功能,让目标对象专注于核心业务,实现横切关注点的集中管理-5。
二、静态代理:最基础的实现方式
2.1 什么是静态代理
静态代理是指代理类由开发者手动编写,在编译期就已确定的代理方式。代理类与目标类实现相同的接口,通过组合方式持有目标对象,在方法执行前后增加扩展逻辑-4。
2.2 生活化类比
静态代理就像为某个明星配备的“专属经纪人”,只服务这一个对象——经纪人只为该明星安排行程、对接活动,不会同时服务其他明星-2。
2.3 代码示例
步骤1:定义业务接口
// 业务接口:定义目标与代理的行为契约 public interface UserService { void addUser(String username); void deleteUser(String username); }
步骤2:实现目标类(真正干活的对象)
// 目标类:专注核心业务,不掺杂任何附加功能 public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("数据库新增用户:" + username); } @Override public void deleteUser(String username) { System.out.println("数据库删除用户:" + username); } }
步骤3:实现代理类(附加功能的“中间人”)
// 静态代理类:为目标类附加日志功能 public class UserServiceProxy implements UserService { private final UserService target; // 持有目标类引用 public UserServiceProxy(UserService target) { this.target = target; } @Override public void addUser(String username) { System.out.println("〖日志〗开始执行addUser,参数:" + username); target.addUser(username); System.out.println("〖日志〗addUser执行完成"); } @Override public void deleteUser(String username) { System.out.println("〖日志〗开始执行deleteUser,参数:" + username); target.deleteUser(username); System.out.println("〖日志〗deleteUser执行完成"); } }
步骤4:调用示例
UserService target = new UserServiceImpl(); UserService proxy = new UserServiceProxy(target); proxy.addUser("张三"); // 输出: // 〖日志〗开始执行addUser,参数:张三 // 数据库新增用户:张三 // 〖日志〗addUser执行完成
2.4 静态代理的优缺点
优点:结构清晰、代码简单、易于理解,方法调用是普通调用,性能开销最小-4。
缺点(核心痛点) :每新增一个接口都需要新增对应的代理类,横切逻辑重复出现,维护成本高;接口和方法越多,代理类数量呈爆炸式增长-4。静态代理适合接口数量较少、结构相对稳定的小型业务系统。
三、动态代理:运行时生成的“万能中间人”
3.1 什么是动态代理
动态代理(Dynamic Proxy) 是指代理类在程序运行时由JVM或字节码框架动态生成,开发者无需手动编写代理类代码。通俗地说,静态代理是在编译时就把代理类写死了,而动态代理是在运行时才“凭空造”出一个代理对象-3。
3.2 生活化类比
租房时,自己找房源很麻烦,可以找房产中介。静态代理相当于你给某个房东找了一个专属中介,只服务这一个房东;而动态代理相当于你走进一家房屋中介公司,他们能根据你的需求(接口)现场分配一个万能中介来帮你处理——你不需要知道中介是谁、怎么来的,只需要知道他能帮你办事-3。
3.3 动态代理与静态代理的关系
本质上,动态代理只是对静态代理中“代理对象的生成”环节进行了改进——从编写固定的代理类代码,改为在运行时动态生成代理对象。通过这一改进,同一个代理生成器可以为任意目标对象创建代理,实现了从“一对一”到“一对多”的飞跃-5。
四、JDK动态代理:Java原生的接口代理
4.1 什么是JDK动态代理
JDK动态代理(JDK Dynamic Proxy) 是Java标准库(java.lang.reflect包)提供的动态代理技术。它在运行时动态生成一个实现了指定接口的代理类,要求目标对象必须实现至少一个接口-13。
4.2 核心组件
JDK动态代理的核心是Proxy类和InvocationHandler接口-3。
Proxy:提供
newProxyInstance()静态方法,用于生成代理对象;InvocationHandler:代理对象的方法调用会被转发到该接口的
invoke()方法,所有增强逻辑集中在这里编写。
4.3 代码示例
步骤1:定义接口和实现类
// 业务接口 public interface UserService { void addUser(String username); } // 目标类:实现接口,专注业务逻辑 public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("数据库新增用户:" + username); } }
步骤2:实现InvocationHandler(统一拦截器)
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class LogInvocationHandler implements InvocationHandler { private final Object target; // 持有目标对象引用 public LogInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前置增强:方法执行前 System.out.println("〖JDK动态代理〗调用方法:" + method.getName() + ",参数:" + args[0]); // 调用目标对象的原始方法(通过反射) Object result = method.invoke(target, args); // 后置增强:方法执行后 System.out.println("〖JDK动态代理〗方法执行完成"); return result; } }
步骤3:生成代理对象并调用
UserService target = new UserServiceImpl(); LogInvocationHandler handler = new LogInvocationHandler(target); UserService proxy = (UserService) Proxy.newProxyInstance( UserService.class.getClassLoader(), // 类加载器 new Class<?>[]{UserService.class}, // 接口数组 handler // InvocationHandler实例 ); proxy.addUser("李四"); // 输出: // 〖JDK动态代理〗调用方法:addUser,参数:李四 // 数据库新增用户:李四 // 〖JDK动态代理〗方法执行完成
4.4 Proxy.newProxyInstance的三个参数
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)的三个参数各司其职-22:
| 参数 | 作用 | 注意事项 |
|---|---|---|
ClassLoader | 加载动态生成的代理类 | 不能为null,需和目标接口在同一类加载器下 |
interfaces | 声明代理要实现的接口列表 | 不能为空,必须是接口类型,不能是普通类 |
InvocationHandler | 拦截所有方法调用的钩子 | 若invoke()中忘记调用method.invoke(),原方法不会执行 |
五、CGLIB动态代理:基于继承的字节码代理
5.1 什么是CGLIB动态代理
CGLIB(Code Generation Library) 是一个基于字节码技术的动态代理库。它通过动态生成目标类的子类来实现代理——即使目标类没有实现任何接口,CGLIB也能为其创建代理对象-50。
5.2 核心组件
CGLIB的核心是Enhancer类和MethodInterceptor接口-32。
Enhancer:增强器,负责配置父类、回调等参数,并生成字节码-30;
MethodInterceptor:方法拦截器,拦截所有非final方法的调用,在其中植入增强逻辑。
5.3 代码示例
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; // 目标类:没有实现任何接口 public class UserService { public void addUser(String username) { System.out.println("数据库新增用户:" + username); } } // 方法拦截器 public class LogMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("〖CGLIB动态代理〗调用方法:" + method.getName() + ",参数:" + args[0]); Object result = proxy.invokeSuper(obj, args); // 调用父类原始方法 System.out.println("〖CGLIB动态代理〗方法执行完成"); return result; } } // 使用示例 public class Main { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); // 设置父类 enhancer.setCallback(new LogMethodInterceptor()); // 设置回调 UserService proxy = (UserService) enhancer.create(); proxy.addUser("王五"); } }
5.4 关键注意点
必须显式指定父类和回调:
setSuperclass()和setCallback()缺一不可-32;调用
invokeSuper而非method.invoke:CGLIB中使用MethodProxy.invokeSuper()来调用父类原始方法,若误用反射method.invoke()会导致无限递归-32;无法代理final类和final方法:CGLIB通过继承生成子类,遇到
final关键字会直接抛出IllegalArgumentException-31。
六、JDK动态代理 vs CGLIB:核心区别一览
6.1 一句话概括
JDK动态代理是“接口代理”,CGLIB是“子类代理”;前者基于反射,后者基于字节码继承。
6.2 详细对比
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 实现原理 | 运行时动态生成实现接口的代理类,通过反射调用目标方法 | 运行时动态生成目标类的子类,通过字节码技术重写非final方法-49 |
| 接口要求 | 必须有接口,否则无法代理-13 | 无需接口,任何非final类均可代理-50 |
| 依赖 | Java原生支持,无需第三方依赖-49 | 需要引入CGLIB库(Spring 5+已内置)-49 |
| 代理限制 | 只能代理接口中定义的方法 | 无法代理final类和final方法;private/static方法也无法代理-49 |
| 类名特征 | $Proxy0、$Proxy1格式-31 | 目标类$$EnhancerByCGLIB$$随机值格式-31 |
| 性能 | 代理对象创建快,但方法调用有反射开销;JDK 8+性能已显著优化- | 首次生成字节码较慢,但方法调用执行效率更高(直接操作)-49 |
| Spring默认策略 | 目标类有接口时默认使用 | 目标类无接口时自动切换;Spring Boot 2.x默认改为CGLIB- |
6.3 执行流程对比
JDK动态代理流程:
调用代理对象方法 → InvocationHandler.invoke() → 反射调用 method.invoke(target, args) → 目标方法执行CGLIB动态代理流程:
调用代理对象方法 → MethodInterceptor.intercept() → MethodProxy.invokeSuper() → 父类原始方法执行七、底层原理:反射与字节码生成
7.1 JDK动态代理的底层机制
JDK动态代理的核心依赖于反射机制。当调用Proxy.newProxyInstance()时,JVM会执行以下操作-3:
生成代理类的字节码:根据传入的接口信息,动态生成一个代理类的字节码,该代理类会继承
Proxy类并实现所有指定的接口-26;加载代理类:使用指定的ClassLoader将生成的字节码加载到JVM中;
创建代理对象实例:通过反射创建代理对象。
可以通过设置系统属性来查看生成的代理类文件:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");7.2 CGLIB动态代理的底层机制
CGLIB的底层依赖于ASM字节码处理框架,通过直接操作字节码来生成新的类-。其核心过程如下-30:
创建
Enhancer实例;通过
setSuperclass()设置目标类为父类;通过
setCallback()设置MethodInterceptor拦截器;create()方法触发字节码生成,使用ASM逐指令构建类文件,生成目标类的子类,并重写所有非final方法,在方法入口插入拦截逻辑-31。
7.3 反射与动态代理的关系
反射技术为JDK动态代理提供了基础支撑——Proxy类的代理生成和Method.invoke()的调用都依赖于Java的反射API。可以说,利用反射技术对传统的静态代理模式进行了改进,才实现了更加灵活通用的动态代理-5。
八、高频面试题与参考答案
面试题1:JDK动态代理和CGLIB动态代理有什么区别?
参考答案:
JDK动态代理和CGLIB动态代理的核心区别体现在以下四个方面:
实现原理不同:JDK基于接口代理,运行时动态生成实现接口的代理类,通过反射调用目标方法;CGLIB基于继承代理,运行时动态生成目标类的子类,通过字节码技术重写非final方法-47。
接口要求不同:JDK要求目标对象必须实现至少一个接口;CGLIB不需要接口,任何非final类都可以代理-49。
依赖不同:JDK是Java原生支持,无需第三方依赖;CGLIB需要引入CGLIB库-49。
代理限制不同:JDK只能代理接口中定义的方法;CGLIB无法代理final类和final方法,也无法代理private/static方法-49。
面试题2:Spring AOP底层默认使用哪种动态代理?
参考答案:
Spring Framework默认使用JDK动态代理。Spring从3.2版本开始内置了CGLIB,当目标类没有实现接口时,会自动切换到CGLIB。Spring Boot 2.x版本则将默认值改成了CGLIB-。
Spring AOP的代理选择逻辑是:如果目标类实现了接口且没有强制使用CGLIB,优先使用JDK动态代理;否则使用CGLIB-。可以通过@EnableAspectJAutoProxy(proxyTargetClass = true)强制使用CGLIB-32。
面试题3:为什么CGLIB无法代理final类或final方法?
参考答案:
CGLIB的实现原理是基于继承——运行时动态生成目标类的子类作为代理类,并通过重写父类方法来实现拦截-50。而final关键字的核心语义是禁止继承(final类)或禁止重写(final方法),这与CGLIB的继承机制根本冲突,因此CGLIB无法代理final类和final方法-49。
面试题4:如何查看JDK动态代理生成的代理类?
参考答案:
可以通过设置系统属性让JVM将生成的代理类保存到文件中-3:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");设置后再次运行动态代理代码,即可在项目目录下找到生成的代理类文件。打开后可以看到代理类继承Proxy类、实现指定接口,并在每个方法中调用了InvocationHandler.invoke()。
面试题5:InvocationHandler的invoke方法中的三个参数分别是什么?
参考答案:
invoke(Object proxy, Method method, Object[] args)的三个参数作用如下-22:
proxy:生成的代理实例本身,通常用不到;
method:被调用的目标方法对象,可用于判断是否需要增强(如只对特定方法加日志);
args:运行时传入的实参数组,永远非null(无参方法为
new Object[0])。
九、结尾总结
本文围绕Java动态代理技术,从痛点出发依次讲解了以下核心知识点:
| 知识点 | 核心要点 |
|---|---|
| 静态代理 | 编译期确定代理关系,简单直接但扩展性差,代理类数量会随接口增加而爆炸 |
| JDK动态代理 | Java原生方案,基于接口+反射,要求目标类必须有接口,通过Proxy.newProxyInstance()生成代理 |
| CGLIB动态代理 | 第三方字节码方案,基于继承+ASM,不依赖接口,但不能代理final类/方法 |
| 核心区别 | JDK是“接口代理”,CGLIB是“子类代理”;前者基于反射,后者基于字节码继承 |
| 底层原理 | JDK依赖反射机制,CGLIB依赖ASM字节码生成框架 |
重点提醒:面试时务必区分清楚两者的实现原理和适用场景;日常开发中,有接口优先考虑JDK动态代理(无额外依赖),无接口则用CGLIB。Spring AOP已自动处理好两者切换,多数情况下开发者无需手动干预。
下一篇预告:深入Spring AOP源码,剖析AnnotationAwareAspectJAutoProxyCreator的代理创建全链路,以及MethodInterceptor拦截链的执行模型。敬请期待!