第30题:JDK动态代理和CGLIB动态代理有什么区别
📚 回答:
- 核心对比 :
JDK动态代理和CGLIB动态代理是两种常用的动态代理实现方式,它们在底层原理、使用场景和限制条件上都有显著差异。以下是详细对比:
1. JDK动态代理
-
定义 :
JDK动态代理基于接口实现,要求目标对象必须实现至少一个接口。
-
实现步骤:
- 目标类:需要实现一个接口。
- 处理类 :实现
InvocationHandler接口,编写增强逻辑(如日志记录、权限校验等)。 - 生成代理对象 :通过
Proxy.newProxyInstance方法动态生成代理对象。
💡 代码示例 :
以下代码展示了JDK动态代理的基本实现:
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Service {
void execute();
}
class TargetService implements Service {
@Override
public void execute() {
System.out.println("目标对象执行业务逻辑");
}
}
class DynamicProxyHandler implements InvocationHandler {
private Object target;
public DynamicProxyHandler(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;
}
}
public class Main {
public static void main(String[] args) {
Service target = new TargetService();
Service proxy = (Service) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new DynamicProxyHandler(target)
);
proxy.execute();
}
}
-
底层原理:
Proxy.newProxyInstance方法会根据传入的目标类加载器和接口信息,动态生成一个代理类的字节数组。- 使用
defineClass0(本地方法)将字节数组加载为代理类实例。 - 代理类实现了目标接口,代理对象调用接口方法时,会触发
InvocationHandler的invoke方法。
-
使用场景:
- AOP编程(如Spring中的事务管理)。
- MyBatis中Mapper接口的动态实现。
2. CGLIB动态代理
-
定义 :
CGLIB(Code Generation Library)基于继承实现,适用于没有实现接口的目标对象。
-
实现步骤:
- 目标类 :无需实现接口,但不能是
final类或包含final方法。 - 处理类 :实现
MethodInterceptor接口,编写增强逻辑。 - 生成代理对象 :通过
Enhancer类动态生成代理对象。
💡 代码示例 :
以下代码展示了CGLIB动态代理的基本实现:
- 目标类 :无需实现接口,但不能是
java
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
class TargetService {
public void execute() {
System.out.println("目标对象执行业务逻辑");
}
}
class CglibProxy implements MethodInterceptor {
@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;
}
}
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetService.class);
enhancer.setCallback(new CglibProxy());
TargetService proxy = (TargetService) enhancer.create();
proxy.execute();
}
}
-
底层原理:
- CGLIB通过ASM技术动态生成目标类的子类,并重写目标方法。
- 当调用代理对象的方法时,实际调用的是子类的重写方法,该方法会触发
MethodInterceptor的intercept方法。
-
使用场景:
- Spring框架中对未实现接口的Bean进行AOP代理。
- Hibernate框架中的延迟加载机制。
3. 对比总结
| 特性 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 实现方式 | 基于接口 | 基于继承 |
| 目标类要求 | 必须实现接口 | 无需实现接口,但不能是final类 |
| 性能 | 性能略高 | 性能稍低(生成子类开销较大) |
| 适用场景 | 目标类实现接口的场景 | 目标类未实现接口的场景 |
💡 面试官视角:
- 面试官可能会问"为什么Spring默认优先使用JDK动态代理?"
答:因为JDK动态代理性能更高,且大多数Spring Bean都会实现接口。 - 面试官可能会追问"CGLIB有哪些局限性?"
答:无法代理final类或final方法,因为CGLIB通过继承实现,而final修饰的类或方法无法被继承或重写。
📌 专栏 :大白话说Java面试题 --- 01-Java基础篇