面试必问!JDK动态代理和CGLIB动态代理的核心区别
那场"代理风波"的开始
上个月的技术面试,当面试官问到"Spring AOP底层用的是什么代理技术"时,我信心满满地回答:"动态代理啊!"然后他又问:"JDK动态代理和CGLIB有什么区别?"
瞬间懵了。虽然平时用Spring框架时代理功能运行得好好的,但要说出具体区别,我还真说不清楚。回家后决定好好研究一下这个"面试杀手"问题。
初探:两个"代理门派"的身世
就像武侠小说里的两大门派,JDK动态代理和CGLIB各有自己的"武功心法":
JDK动态代理:
- 身世:JDK原生支持,从1.3版本就有了
- 特点:只能代理实现了接口的类
- 原理:基于接口实现,生成接口的实现类
CGLIB动态代理:
- 身世:第三方库,Code Generation Library的缩写
- 特点:可以代理普通类,不需要接口
- 原理:基于继承,生成目标类的子类
动手实践:看看它们的"招式"
JDK动态代理的"轻功"
java
// 目标接口和实现
interface UserService {
void saveUser(String name);
}
// JDK动态代理的核心
Object proxyInstance = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("前置处理...");
Object result = method.invoke(target, args);
System.out.println("后置处理...");
return result;
}
);
CGLIB的"内功心法"
java
// 不需要接口,直接代理普通类
public class UserServiceImpl {
public void saveUser(String name) {
System.out.println("保存用户: " + name);
}
}
// CGLIB代理创建
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("CGLIB前置处理...");
Object result = proxy.invokeSuper(obj, args);
System.out.println("CGLIB后置处理...");
return result;
});
UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
踩坑瞬间:不是所有类都能被"代理"
研究过程中遇到了几个坑:
JDK代理的限制:
- 目标类必须实现接口,否则直接报错
- 只能拦截接口中定义的方法
CGLIB的"软肋":
- final类无法被继承,代理创建失败
- final方法无法被重写,拦截不到
- private方法也拦截不到
最让我印象深刻的是,当我试图用CGLIB代理一个final类时:
java
public final class FinalService {
public void doSomething() {
System.out.println("final类的方法");
}
}
// 运行时抛出异常:Cannot subclass final class
性能PK:速度与灵活性的较量
通过简单的性能测试发现了有趣的现象:
对比维度 | JDK动态代理 | CGLIB |
---|---|---|
代理创建速度 | 快 | 慢(需要生成字节码) |
方法调用速度 | 慢(反射调用) | 快(直接调用) |
内存占用 | 小 | 大(生成更多类) |
适用场景 | 接口代理 | 类代理 |
这就像是"轻功"与"内功"的区别:JDK代理启动快但调用慢,CGLIB启动慢但调用快。
经验启示:Spring框架的"智慧选择"
研究完才明白,Spring AOP为什么这么"聪明":
- 默认策略:目标类实现了接口 → 用JDK代理;否则 → 用CGLIB
- 可配置 :通过
proxyTargetClass=true
强制使用CGLIB - 最佳实践:尽量基于接口编程,让JDK代理发挥作用
实际开发建议:
- 设计时优先考虑接口,让JDK代理处理大部分场景
- 对于第三方类或无法修改的类,CGLIB是救星
- 性能敏感的场景,选择CGLIB(如高频调用的方法)
- 简单的AOP场景,JDK代理足够了
总结:代理技术的"阴阳平衡"
JDK动态代理和CGLIB就像技术世界的"阴阳两极",各有所长:
- JDK代理:轻量、原生、基于接口,适合规范的面向对象设计
- CGLIB:强大、灵活、基于继承,能处理更复杂的代理需求
在面试中,关键不是死记硬背区别,而是理解它们的设计思想和适用场景。毕竟,没有最好的技术,只有最合适的选择。
现在再遇到这个面试题,我可以从容地说出:"这两种代理技术在不同场景下各有优势,Spring框架的智能选择策略正是这种技术平衡的体现。"
本文转自渣哥zha-ge.cn/java/3