在Java开发中,有一个知识点既是设计模式的经典案例,也是Spring AOP的底层基石,面试时几乎必问、工作中每天都在用——它就是动态代理。然而很多学习者只会在框架里配置,搞不清JDK动态代理和CGLIB到底有什么区别,更说不透“为什么JDK只能代理接口”背后的原理。本文围绕AI助手She整理的JDK动态代理与CGLIB两大核心方案,从静态代理痛点切入,讲解两种代理方式的原理差异、适用场景、代码示例和面试高频题,帮助读者彻底掌握动态代理的全链路知识。
一、痛点切入:为什么需要动态代理

假设我们要给一个用户服务类的方法添加日志功能。最简单的做法是直接在方法里加System.out.println,但这种方式会污染核心业务代码,而且每新增一个方法都要重复写一遍。来看静态代理是如何实现的:
// 业务接口public interface UserService { void register(); void login(); } // 目标类(真正的业务逻辑) public class UserServiceImpl implements UserService { @Override public void register() { System.out.println("注册用户"); } @Override public void login() { System.out.println("登录用户"); } } // 静态代理类——手写!每个接口都要单独写一个 public class UserServiceProxy implements UserService { private final UserService target; public UserServiceProxy(UserService target) { this.target = target; } @Override public void register() { System.out.println("〖前置〗记录日志"); target.register(); System.out.println("〖后置〗结束日志"); } @Override public void login() { System.out.println("〖前置〗记录日志"); target.login(); System.out.println("〖后置〗结束日志"); } }
静态代理存在三大致命问题:
类爆炸:每个被代理的接口都需要手动编写一个代理类。当系统有UserService、OrderService、ProductService等多个接口时,代理类数量会急剧膨胀-。
代码冗余:日志、事务、权限等横切逻辑在每一个代理类中重复出现,维护成本极高-。
扩展性差:当接口新增方法时,目标类和代理类都需要同步修改,违反开闭原则-。
这些痛点的根源在于:代理类需要开发者提前手动编写,无法在运行时自动生成。而动态代理的出现,正是为了解决这一问题——它让代理类在运行时由JVM动态生成,开发者只需要写一次增强逻辑即可-。
二、核心概念:JDK动态代理(概念A)
2.1 标准定义
JDK动态代理(Java Development Kit Dynamic Proxy)是Java标准库提供的动态代理实现,从JDK 1.3版本开始引入,位于java.lang.reflect包中-12。它的核心机制是:在运行时动态生成一个实现了指定接口的代理类,并将所有方法调用转发给InvocationHandler统一处理-54。
2.2 核心三组件
JDK动态代理依赖三大核心组件-6-54:
| 组件 | 作用 | 生活类比 |
|---|---|---|
| 接口 | 定义代理对象的行为规范 | 演员的剧本 |
| InvocationHandler | 代理逻辑的控制中心,实现invoke()方法 | 导演的调度室 |
| Proxy | 通过newProxyInstance()动态生成代理实例 | 选角导演 |
2.3 工作原理
JDK动态代理的底层工作流程如下:
调用
Proxy.newProxyInstance(ClassLoader, interfaces, handler),传入类加载器、接口数组和InvocationHandler实例-3。Proxy类在内存中动态生成一个实现所有指定接口的代理类字节码(类名形如
$Proxy0),该类继承了Proxy基类-。代理类中所有接口方法的实现都会调用
InvocationHandler.invoke()方法-7。在
invoke()方法中,开发者可以编写增强逻辑(日志、事务、权限等),再通过反射调用目标对象的原始方法-11。
核心机制是“反射 + Proxy” :利用反射在运行时获取方法信息,通过Proxy类动态生成代理字节码-11。
三、关联概念:CGLIB动态代理(概念B)
3.1 标准定义
CGLIB(Code Generation Library,代码生成库)是一个开源的、高性能的Java字节码生成库,它通过在运行时动态生成目标类的子类来实现动态代理-62。CGLIB弥补了JDK动态代理的局限性,可以代理没有实现接口的普通类。
3.2 核心组件与原理
CGLIB的核心组件包括-62:
Enhancer:增强器类,负责配置和生成代理对象。
MethodInterceptor:方法拦截器接口,开发者实现其
intercept()方法来编写增强逻辑。
CGLIB的工作原理是-64:
通过ASM字节码处理框架在内存中动态生成目标类的子类。
代理子类会重写父类中的所有非final方法。
在重写的方法中,调用
MethodInterceptor.intercept()执行增强逻辑。通过
proxy.invokeSuper()调用父类(目标类)的原始方法。
注意:由于CGLIB基于继承实现,它无法代理被final修饰的类或方法-3。
四、概念关系与区别:JDK vs CGLIB
两种动态代理方案的核心差异总结如下-3-11:
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 核心原理 | 基于接口,通过反射生成代理类 | 基于继承,通过ASM生成子类 |
| 实现方式 | Proxy.newProxyInstance() + InvocationHandler | Enhancer + MethodInterceptor |
| 必要条件 | 目标类必须实现至少一个接口 | 无需接口,但类/方法不能是final |
| 底层技术 | 反射 + Proxy | ASM字节码增强 + FastClass机制 |
| 依赖库 | Java原生,无需额外依赖 | 需引入CGLIB库(Spring Core已内置) |
| 代理创建速度 | 较快(无需生成字节码) | 较慢(需动态生成字节码) |
| 方法调用性能 | JDK 8以前较慢;JDK 8+优化后差距缩小 | 快(通过FastClass索引调用,避免反射开销) |
| 典型应用场景 | Spring AOP代理有接口的Bean | Spring AOP代理无接口的Bean |
一句话概括:JDK动态代理是“面向接口的代理方案”,CGLIB是“面向类的代理方案”——前者是Java原生轻量级方案,后者是功能更强大的字节码增强方案。
五、代码示例:极简实现与流程解析
5.1 JDK动态代理示例
// 1. 定义接口(必须!) public interface UserService { void addUser(String name); } // 2. 目标类实现接口 public class UserServiceImpl implements UserService { @Override public void addUser(String name) { System.out.println("添加用户:" + name); } } // 3. 实现InvocationHandler(增强逻辑写在这里) public class LogHandler implements InvocationHandler { private final Object target; public LogHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("【前置】方法:" + method.getName()); Object result = method.invoke(target, args); // 反射调用目标方法 System.out.println("【后置】方法执行完毕"); return result; } } // 4. 使用Proxy.newProxyInstance创建代理对象 public class Main { public static void main(String[] args) { UserService target = new UserServiceImpl(); UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new LogHandler(target) ); proxy.addUser("张三"); } }
执行流程:调用proxy.addUser() → 代理类自动转发到LogHandler.invoke() → 执行前置增强 → 通过method.invoke()反射调用目标对象方法 → 执行后置增强 → 返回结果-15。
5.2 CGLIB动态代理示例
// 1. 目标类(无需实现接口) public class UserService { public void addUser(String name) { System.out.println("添加用户:" + name); } } // 2. 实现MethodInterceptor public class LogInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("【前置】方法:" + method.getName()); Object result = proxy.invokeSuper(obj, args); // 调用父类原始方法 System.out.println("【后置】方法执行完毕"); return result; } } // 3. 使用Enhancer创建代理 public class Main { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); enhancer.setCallback(new LogInterceptor()); UserService proxy = (UserService) enhancer.create(); proxy.addUser("张三"); } }
执行流程:调用proxy.addUser() → 实际调用的是CGLIB生成的子类重写方法 → 子类方法内部调用LogInterceptor.intercept() → 执行增强逻辑 → 通过proxy.invokeSuper()调用父类原始方法-64。
六、底层原理与技术支撑
6.1 JDK动态代理的底层依赖:反射
JDK动态代理的核心底层依赖是Java反射机制(Reflection) 。反射允许程序在运行时获取类的结构信息(类名、方法、字段等),并动态调用方法、创建对象-1。
性能特点:反射调用(如Method.invoke())通常比直接调用慢5到50倍,主要开销来自安全检查、装箱/拆箱、间接分派以及无法进行静态内联优化-1。JDK 8及后续版本对反射机制进行了持续优化,性能差距已显著缩小-3。
为什么JDK动态代理只能代理接口?
因为Proxy机制底层生成的代理类(如$Proxy0)继承了java.lang.reflect.Proxy类,而Java不支持多重继承,因此代理类无法再继承目标具体类,只能通过实现接口的方式来提供方法-41。
6.2 CGLIB的底层依赖:ASM字节码框架
CGLIB的底层依赖是ASM字节码处理框架,这是一个小且快的Java字节码操作库,能够直接读写Java类的字节码。ASM允许CGLIB在运行时解析、修改和生成新的.class文件-62。
FastClass机制——CGLIB的性能秘籍:
CGLIB通过FastClass机制避免反射调用带来的性能损耗。具体做法是:
为目标类和代理类分别生成一个FastClass文件。
FastClass包含方法名到整数索引的映射表。
方法调用时通过索引直接调用,类似数组下标访问,绕过了反射的开销--65。
代价:CGLIB生成代理时需要生成三份字节码文件(代理类 + 目标类FastClass + 代理类FastClass),以空间换时间-。
七、高频面试题与参考答案
面试题1:JDK动态代理和CGLIB动态代理有什么区别?
参考答案(踩分点:原理 + 条件 + 性能 + 应用):
实现原理不同:JDK动态代理基于接口,通过反射和Proxy类生成代理类;CGLIB基于继承,通过ASM字节码框架生成目标类的子类。
使用条件不同:JDK要求目标类必须实现接口;CGLIB不要求接口,但无法代理final类或final方法。
性能表现不同:JDK生成代理对象更快,但早期版本反射调用开销较大;JDK 8+优化后两者差距缩小。CGLIB生成代理对象较慢,但运行时调用效率更高(通过FastClass机制)。
依赖不同:JDK是Java原生支持,无额外依赖;CGLIB需要引入第三方库(Spring Boot 3.x默认内置)。
Spring AOP中的选择策略:Spring默认优先使用JDK动态代理;当目标Bean没有实现接口时,自动降级为CGLIB-3-37。
面试题2:Spring AOP的底层实现原理是什么?
参考答案:
Spring AOP的核心实现依赖于动态代理技术。当容器启动时,Spring会根据目标Bean是否实现接口来选择合适的代理方式:
若目标Bean实现了接口,Spring默认使用JDK动态代理,生成一个实现相同接口的代理对象。
若目标Bean没有实现接口,Spring使用CGLIB,通过生成目标类的子类来创建代理对象。
代理对象会拦截目标方法的调用,在方法执行前后织入增强逻辑(如@Before、@After、@Around等),从而实现横切关注点与业务逻辑的解耦--54。
面试题3:为什么JDK动态代理只能代理接口?
参考答案(踩分点:继承限制 + Proxy源码分析):
JDK动态代理生成的代理类(如$Proxy0)会继承java.lang.reflect.Proxy类。由于Java不支持多重继承,该代理类无法再继承目标具体类,因此只能通过实现接口的方式来提供被代理的方法。如果传入一个没有实现接口的类,Proxy.newProxyInstance()会抛出IllegalArgumentException: interface is required--41。
面试题4:CGLIB能否代理final类?为什么?
参考答案:
CGLIB无法代理final类,也无法代理final方法。因为CGLIB的实现原理是动态生成目标类的子类,通过继承和重写方法来达到代理目的。final类不能被继承,final方法不能被重写,因此CGLIB无法对它们进行代理-3-62。
面试题5:动态代理和静态代理的区别是什么?
参考答案:
| 对比维度 | 静态代理 | 动态代理 |
|---|---|---|
| 代理类生成时机 | 编译期由开发者手动编写 | 运行时由JVM/字节码框架动态生成 |
| 代码量 | 每个目标类需单独编写代理类 | 只需编写一次InvocationHandler/MethodInterceptor |
| 灵活性 | 差,新增方法需修改代理类 | 好,可统一处理所有方法 |
| 维护成本 | 高,接口越多代理类越多 | 低,横切逻辑集中管理 |
| 性能 | 直接调用,无额外开销 | 有反射或字节码调度的微小开销 |
一句话总结:静态代理在编译期“写死”代理关系,动态代理在运行时“生成”代理关系--45。
八、总结
本文围绕Java动态代理,从静态代理的痛点出发,依次讲解了:
JDK动态代理:Java原生方案,基于接口 + 反射 + Proxy,轻量但要求目标类必须实现接口。
CGLIB动态代理:第三方方案,基于继承 + ASM字节码生成,功能强大但不支持final类/方法。
核心区别:JDK是“面向接口的代理”,CGLIB是“面向类的代理”;底层分别依赖反射和字节码增强。
底层原理:JDK依赖反射调用,CGLIB依赖FastClass索引调用以优化性能。
Spring AOP应用:根据目标Bean是否有接口自动选择代理方式。
记忆口诀:接口优先JDK,无接口用CGLIB;final方法别代理,反射性能已优化。
后续文章将继续深入Spring AOP的源码实现,讲解代理选择策略的底层判断逻辑,以及如何通过配置强制指定代理方式。欢迎持续关注!

