问题
动态代理有哪些实现方式?各自的优缺点是什么?
答案
一、核心概念
动态代理是在运行时动态生成代理类的技术,无需手动编写代理类代码。Java 中主要有以下几种实现方式:
- JDK 动态代理(基于接口)
- CGLIB 动态代理(基于继承)
- Javassist(字节码操作)
- ByteBuddy(现代字节码生成库)
二、JDK 动态代理
实现原理
基于 Java 反射机制,通过 java.lang.reflect.Proxy 类和 InvocationHandler 接口实现。代理类在运行时动态生成,必须实现一个或多个接口。
代码示例
// 接口定义
interface UserService {
void addUser(String name);
String getUser(int id);
}
// 目标类
class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
@Override
public String getUser(int id) {
return "用户" + id;
}
}
// InvocationHandler 实现
class LogHandler implements InvocationHandler {
private 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("方法调用后: " + method.getName());
return result;
}
}
// 使用
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogHandler(target)
);
proxy.addUser("张三");
优缺点
优点��
- JDK 原生支持,无需额外依赖
- 性能较好(JDK 8+ 优化后)
- 代码简洁,易于理解
缺点:
- 只能代理实现了接口的类
- 无法代理 final 方法
- 反射调用有一定性能开销
三、CGLIB 动态代理
实现原理
基于 ASM 字节码框架,通过继承目标类生成子类,重写父类方法实现代理。不要求目标类实现接口。
代码示例
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
// 目标类(无需实现接口)
class OrderService {
public void createOrder(String orderId) {
System.out.println("创建订单: " + orderId);
}
}
// MethodInterceptor 实现
class LogInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("CGLIB 前置增强: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("CGLIB 后置增强: " + method.getName());
return result;
}
}
// 使用
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class);
enhancer.setCallback(new LogInterceptor());
OrderService proxy = (OrderService) enhancer.create();
proxy.createOrder("ORDER-001");
优缺点
优点:
- 可以代理没有实现接口的类
- 性能优于 JDK 动态代理(方法调用不走反射)
- Spring AOP 默认使用 CGLIB
缺点:
- 需要额外依赖(cglib.jar)
- 无法代理 final 类和 final 方法
- 生成的子类会增加内存开销
- 构造函数会被调用两次
四、Javassist
实现原理
直接操作字节码,提供高层 API 简化字节码编程。可以在运行时修改类或生成新类。
代码示例
import javassist.*;
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("com.example.DynamicService");
// 添加方法
CtMethod method = CtNewMethod.make(
"public void sayHello() { System.out.println(\"Hello from Javassist\"); }",
ctClass
);
ctClass.addMethod(method);
// 生成类
Class<?> clazz = ctClass.toClass();
Object instance = clazz.newInstance();
clazz.getMethod("sayHello").invoke(instance);
优缺点
优点:
- 功能强大,可以直接修改字节码
- API 相对友好,支持源码级别的字节码操作
- 性能优秀
缺点:
- 学习曲线较陡
- 调试困难
- 不当使用可能导致类加载问题
五、ByteBuddy
实现原理
现代化的字节码生成库,提供流式 API,比 Javassist 更易用。被 Mockito、Hibernate 等框架采用。
代码示例
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;
Class<?> dynamicType = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value("Hello ByteBuddy"))
.make()
.load(getClass().getClassLoader())
.getLoaded();
System.out.println(dynamicType.newInstance().toString());
// 输出: Hello ByteBuddy
优缺点
优点:
- API 设计优雅,易于使用
- 性能优秀,生成代码质量高
- 支持 Java 8+ 新特性
- 类型安全
缺点:
- 相对较新,社区资源较少
- 包体积较大
六、性能对比
| 实现方式 | 创建速度 | 调用速度 | 内存占用 |
|---|---|---|---|
| JDK 动态代理 | 快 | 中等(反射) | 小 |
| CGLIB | 慢(生成子类) | 快(直接调用) | 较大 |
| Javassist | 中等 | 快 | 中等 |
| ByteBuddy | 中等 | 快 | 中等 |
七、实际应用场景
JDK 动态代理:
- Spring AOP(目标类实现接口时)
- MyBatis Mapper 接口代理
- RPC 框架的客户端代理
CGLIB:
- Spring AOP(目标类未实现接口时)
- Hibernate 懒加载
- Mock 框架(Mockito)
Javassist/ByteBuddy:
- 字节码增强框架(SkyWalking、Arthas)
- ORM 框架的实体增强
- 热部署工具
八、面试答题要点
- JDK vs CGLIB:JDK 基于接口,CGLIB 基于继承;JDK 用反射,CGLIB 用字节码
- 选择依据:有接口用 JDK,无接口用 CGLIB;Spring 默认策略是有接口用 JDK,否则用 CGLIB
- 性能考量:CGLIB 创建慢但调用快,JDK 相反;实际应用中差异不大
- 限制条件:JDK 需要接口,CGLIB 不能代理 final 类/方法
- 实际应用:能说出 Spring AOP、MyBatis、RPC 框架等具体使用场景