在Java后端面试中,动态代理几乎是被问及频率最高的基础知识点之一——它不仅贯穿于Spring AOP的核心实现,更是面试官考察候选人基本功深度的“照妖镜”。许多开发者在日常开发中仅限于调用代理方法,一旦被追问“JDK动态代理为什么只能代理接口”或“CGLIB底层是如何生成代理类的”,往往答不上来。本文将围绕Java动态代理这一核心技术,从痛点切入、逐步拆解概念、用代码示例对比展示JDK Proxy和CGLIB两种实现方式,最后提炼高频面试考点,帮助读者真正吃透这一知识点。
一、痛点切入:为什么需要动态代理?

先看一个典型场景。假设你有一个发送短信的业务类,现在需要在每个方法执行前后记录日志。静态代理的做法是:手动为每个被代理类创建一个代理类,实现相同接口,并在代理类的方法中嵌入增强逻辑。
// 1. 定义接口public interface SmsService { String send(String message); } // 2. 目标实现类 public class SmsServiceImpl implements SmsService { @Override public String send(String message) { System.out.println("send message: " + message); return message; } } // 3. 静态代理类 public class SmsProxy implements SmsService { private final SmsService smsService; public SmsProxy(SmsService smsService) { this.smsService = smsService; } @Override public String send(String message) { System.out.println("before method send()"); // 前置增强 String result = smsService.send(message); System.out.println("after method send()"); // 后置增强 return result; } }
使用静态代理时,调用端需要通过代理类间接访问目标对象:
public class Main { public static void main(String[] args) { SmsService smsService = new SmsServiceImpl(); SmsProxy smsProxy = new SmsProxy(smsService); smsProxy.send("java"); } }
运行结果:
before method send() send message: java after method send()
静态代理的致命缺陷:
代码冗余严重:每增加一个目标类,就需要为其创建一个代理类,代码量翻倍-27。
扩展性极差:接口一旦新增方法,所有代理类和目标类都要同步修改-1。
维护成本高:横切逻辑(如日志、事务)散落在各个代理类中,无法复用和集中管理-28。
动态代理正是为解决这些痛点而生的——它能在运行时动态生成代理类,一套增强逻辑可以被无限复用。
二、核心概念讲解:动态代理(Dynamic Proxy)
标准定义:动态代理(Dynamic Proxy)是一种在程序运行时动态创建代理类和代理对象的机制,代理对象代替真实对象执行方法调用,并可在调用前后插入额外逻辑--。
关键词拆解:
动态:代理类不是手动编写,而是在运行时由JVM动态生成。
代理:代理对象持有目标对象的引用,负责转发方法调用。
生活化类比:动态代理就像一个“万能中介”——静态代理相当于你每次租房都要亲自找一家不同的中介公司(一个目标类对应一个代理类);而动态代理则是你认识了一个“全能中介”,无论你要租哪套房,它都能代理处理,无需重复签约-。
核心价值:将日志记录、权限校验、事务管理等横切关注点(cross-cutting concerns) 从业务逻辑中抽离出来,在不修改原有代码的前提下实现对目标对象的功能增强-36。
三、关联概念讲解:JDK动态代理 vs CGLIB动态代理
Java中动态代理主要有两种实现方式:JDK原生动态代理和CGLIB动态代理。
3.1 JDK动态代理
定义:JDK动态代理是Java原生提供的动态代理机制,位于java.lang.reflect包下,基于反射和接口实现。
核心组件:
Proxy类:提供
newProxyInstance()静态方法,用于动态生成代理对象。InvocationHandler接口:定义了
invoke()方法,代理对象的方法调用会统一转发到此方法中处理-9。
使用条件:目标类必须实现至少一个接口,因为JDK动态代理生成的代理类本质上是一个实现了目标接口的$Proxy类-4。
3.2 CGLIB动态代理
定义:CGLIB(Code Generation Library)是一个基于ASM字节码处理框架的代码生成库,通过动态生成目标类的子类来实现代理-。
核心组件:
Enhancer类:CGLIB的核心生成器,负责配置父类并生成代理类。
MethodInterceptor接口:定义了
intercept()方法,用于拦截代理类的方法调用并植入增强逻辑-18。
使用条件:目标类不能被final修饰(因为CGLIB通过继承生成子类,final类无法被继承),目标方法也不能是final方法-。
四、概念关系与区别总结
JDK动态代理和CGLIB动态代理的关系可以一句话概括:JDK动态代理是基于接口的“组合”思想,CGLIB动态代理是基于继承的“子类化”思想,二者共同构成了动态代理的两大技术支柱-24。
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 底层原理 | 基于反射+接口组合,生成实现接口的$Proxy类 | 基于ASM字节码+继承,生成目标类的子类 |
| 代理对象类型 | 实现了目标接口的代理类 | 目标类的子类 |
| 目标类要求 | 必须实现接口 | 不能是final类,方法不能是final |
| 性能特点 | 创建代理速度快,但执行时反射调用开销略高 | 创建代理耗时较长,执行时直接调用父类方法,性能更高 |
| 依赖 | JDK原生支持,无需第三方库 | 需要引入cglib/asm依赖(Spring已内置打包) |
| 适用场景 | 目标类有接口、单例对象需频繁创建代理时 | 目标类无接口、单例对象创建后长期使用时 |
五、代码示例演示
5.1 JDK动态代理完整示例
// 1. 定义接口(JDK动态代理要求) public interface UserService { void addUser(String username); } // 2. 目标实现类 public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("添加用户:" + username); } } // 3. 实现InvocationHandler接口 import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class LogProxy implements InvocationHandler { private Object target; // 持有目标对象引用 public LogProxy(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始记录日志..."); Object result = method.invoke(target, args); // 反射调用目标方法 System.out.println("结束记录日志..."); return result; } } // 4. 使用代理 public class Main { public static void main(String[] args) { UserService target = new UserServiceImpl(); InvocationHandler handler = new LogProxy(target); UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 类加载器 target.getClass().getInterfaces(), // 接口数组 handler // 调用处理器 ); proxy.addUser("Alice"); } }
执行流程解析:
Proxy.newProxyInstance()在运行时动态生成代理类的字节码并加载到JVM。生成的代理类实现了
UserService接口,并在每个方法内部调用LogProxy.invoke()。调用
proxy.addUser("Alice")时,JVM自动将调用转发到invoke()方法。在
invoke()中先执行前置增强,再通过反射调用target.addUser(),最后执行后置增强-9。
5.2 CGLIB动态代理完整示例
// 1. 目标类(无需实现接口) public class SayHello { public void say() { System.out.println("hello everyone"); } } // 2. 实现MethodInterceptor接口 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 CglibProxy implements MethodInterceptor { public Object getProxy(Class<?> clazz) { Enhancer enhancer = new Enhancer(); // 核心生成器 enhancer.setSuperclass(clazz); // 设置父类(目标类) enhancer.setCallback(this); // 设置拦截器 return enhancer.create(); // 生成代理对象 } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("前置代理..."); Object result = proxy.invokeSuper(obj, args); // 调用父类方法 System.out.println("后置代理..."); return result; } } // 3. 使用代理 public class Main { public static void main(String[] args) { CglibProxy proxy = new CglibProxy(); SayHello proxyObj = (SayHello) proxy.getProxy(SayHello.class); proxyObj.say(); } }
执行流程解析:
Enhancer通过ASM字节码框架在运行时动态生成SayHello的子类。子类重写了所有非final方法,在重写的方法中调用
MethodInterceptor.intercept()。在
intercept()中可以自定义增强逻辑,并通过proxy.invokeSuper()调用父类原始方法-20。
六、底层原理/技术支撑点
两种动态代理的底层技术支撑截然不同:
JDK动态代理的底层核心是“反射机制”。当调用Proxy.newProxyInstance()时,JVM会根据传入的接口数组,在运行时动态生成代理类的字节码并加载。生成的代理类内部,每个方法的实现都等价于handler.invoke(this, method, args)——正是反射让这种运行时动态生成成为可能--9。
CGLIB动态代理的底层核心是“字节码操作技术(ASM)”。CGLIB直接操纵字节码,为目标类动态生成一个子类,并重写其非final方法。这种字节码层面的操作避免了反射调用的开销,执行效率更高,但代价是代理类的生成过程更耗时--20。
简单来说,JDK动态代理靠“反射”在运行时动态生成接口的实现类,CGLIB靠“字节码操作”在运行时动态生成目标类的子类。
七、高频面试题与参考答案
面试题1:什么是动态代理?JDK动态代理和CGLIB有什么区别?
参考答案(踩分点:定义+对比维度):
动态代理是在运行时动态创建代理类的机制,无需手动编写代理类代码。JDK动态代理是Java原生实现,基于反射和接口,要求目标类必须实现接口,生成的代理类是$Proxy类型;CGLIB动态代理基于ASM字节码技术,通过生成目标类的子类实现代理,不要求目标类实现接口,但目标类不能是final的。JDK代理创建速度快但执行时反射开销略高,CGLIB代理创建速度慢但执行性能更好-4-18。
面试题2:为什么JDK动态代理只能代理接口实现类?
参考答案(踩分点:类型系统限制):
JDK动态代理生成的代理类已经继承了java.lang.reflect.Proxy类。Java是单继承语言,一个类无法同时继承两个父类。如果目标类是一个普通类,代理类无法再继承它。JDK动态代理只能通过实现接口的方式与目标类建立联系——代理类实现目标类的接口,并内部持有目标对象的引用,通过组合而非继承的方式完成代理-4。
面试题3:Spring AOP默认使用哪种动态代理?
参考答案(踩分点:Spring的决策逻辑):
Spring AOP默认使用JDK动态代理。当目标类实现了接口时,Spring优先使用JDK动态代理;只有当目标类没有实现任何接口时,Spring才会自动切换为CGLIB动态代理。也可以通过配置<aop:aspectj-autoproxy proxy-target-class="true"/>强制Spring始终使用CGLIB代理-。
面试题4:CGLIB能代理final类吗?为什么?
参考答案(踩分点:继承机制的限制):
不能。CGLIB动态代理的原理是在运行时动态生成目标类的子类,通过继承重写方法来实现代理增强。final类不能被继承,因此CGLIB无法代理final类。同理,final方法也不能被重写,所以final方法也无法被CGLIB代理--24。
面试题5:如何理解动态代理中的“动态”?
参考答案(踩分点:运行时 vs 编译期):
“动态”体现在两个层面:第一,代理类的生成时机是运行时而非编译期,开发者无需手动编写代理类代码;第二,代理关系在运行时才能确定,同一套InvocationHandler/MethodInterceptor可以为任意符合条件的目标类生成代理对象,实现“一套逻辑,无限复用”。相比之下,静态代理的代理关系在编译期就已经确定-35。
八、结尾总结
回顾全文,核心知识点可以归纳为三条主线:
问题驱动:静态代理的代码冗余和扩展性差,催生了动态代理的诞生。
双实现对比:JDK动态代理基于接口组合+反射,CGLIB基于继承+字节码操作,两者各有优劣,适用场景不同。
底层技术:JDK依赖反射机制,CGLIB依赖ASM字节码框架,理解这两者才能透彻理解动态代理的本质。
易错点提醒:
不要混淆“JDK动态代理只能代理接口”和“CGLIB不能代理final类”这两个限制条件的根本原因。
面试回答时,除了说出区别,更要说出“为什么”——比如JDK限制源于单继承,CGLIB限制源于继承机制本身。
下一篇文章将深入Spring AOP源码,分析Spring是如何根据目标类类型自动选择JDK或CGLIB代理的,以及@Transactional注解的代理失效原理,敬请期待。

