12 java面试题: JDK 动态代理 CGLIB 动态代理 的区别和联系是什么
这个问题问得很全面,不仅问了“区别”,还问了“联系”。在面试中,能讲清楚“联系”往往是拉开差距的关键。
我直接为你提炼一套**“区别 + 联系”**的满分回答逻辑,你按这个思路说,面试官一定会认可。
一、核心区别(机制与场景)
先说最本质的区别,一句话概括:
JDK 动态代理是“基于接口”的代理(亲儿子),CGLIB 是“基于继承”的代理(干儿子)。
围绕这个核心,区别体现在以下五个维度:
| 对比维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 实现原理 | 运行时生成一个实现了目标接口的匿名代理类。 | 运行时通过操作字节码(ASM框架),生成一个继承自目标类的子类代理对象。 |
| 目标类要求 | 必须实现至少一个接口。 | 不需要实现接口,普通类即可。 |
| 方法限制 | 只能代理接口中定义的方法(即public抽象方法)。 | 能代理类中所有非final、非static的方法(final方法无法被重写)。 |
| 性能(JDK 1.8+) | 方法调用性能更优(JVM 做了内联优化)。 | 创建代理对象速度较快,但方法调用性能稍慢于 JDK。 |
| 依赖 | JDK 原生自带,无需额外引入包。 | 需引入cglib包(Spring 3.2+ 已内嵌 ASM 源码)。 |
二、深层联系(它们本质上是“兄弟”)
很多候选人只答区别,不提联系。如果你能说出下面这几点,就是加分项:
本质相同(都是运行时字节码增强):
两者都不是在编译期修改源码,而是在JVM 运行时动态生成新的 Class 字节码,并加载到内存中,以此来拦截目标方法,实现无侵入的增强(AOP)。目的相同(都是方法拦截与代理):
最终目的都是为了对目标方法进行增强(如日志、事务、权限校验)。它们都通过一个类似“调度器”的组件来分发调用:- JDK 靠
InvocationHandler.invoke() - CGLIB 靠
MethodInterceptor.intercept()
- JDK 靠
都依赖类加载器(ClassLoader):
动态生成的代理类都必须通过ClassLoader加载到 JVM 中才能使用。在现代框架中“协同共存、互补使用”(这是最重要的联系):
它们不是谁取代谁的关系,而是互补关系。以Spring 框架为例:Spring AOP 默认的底层策略是:如果目标类实现了接口,就用 JDK 代理;如果没有实现接口,就用 CGLIB 代理。
也就是说,Spring 把它们当作两种工具放在工具箱里,按需取用。如果你强制设置
proxy-target-class = true,Spring 也会放弃 JDK,统一用 CGLIB。
三、面试金句(帮你总结)
如果面试官再追问“你觉得哪个更好?”,你可以这样收尾:
“在我看来,它们并不是鱼和熊掌的关系。JDK 动态代理是官方原生方案,轻量高效,但受限于接口;CGLIB 是强大的补充方案,弥补了无接口类的代理需求,但受限于
final关键字。现代开发中,我们应遵循‘接口优先’的设计原则,默认用 JDK 代理;只有在确实没有接口或必须代理非public方法时,才退而求其次选用 CGLIB。”
这样回答,既讲清了区别,又点明了它们在框架层面的联系与协作,层次非常清晰。现在 JDK 和 CGLIB 的区别与联系已经讲透了,你还需要我结合Spring 源码,具体讲讲它是如何通过DefaultAopProxyFactory类智能选择这两种代理方式的吗?想深入的话我可以继续为你拆解。😊
