动态代理核心概念
动态代理是一种运行时动态生成代理类 的机制,用于增强目标对象的方法 (如添加日志、事务、权限控制等),而无需修改目标对象的源码。Java中主要有两种动态代理实现:JDK动态代理 和CGLIB动态代理。
一、JDK动态代理实现原理
JDK动态代理是基于接口 的动态代理,核心依赖java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler接口。
1. 实现条件
- 目标类必须实现至少一个接口
- 代理类是运行时动态生成 的,继承自
Proxy类,实现目标类的所有接口
2. 核心组件
Proxy类 :用于生成代理实例,核心方法newProxyInstance()InvocationHandler接口 :代理类的方法调用处理器,通过invoke()方法增强目标方法
3. 实现流程
- 目标类实现接口
- 创建
InvocationHandler实现类,重写invoke()方法 - 通过
Proxy.newProxyInstance()生成代理实例 - 调用代理实例的方法,最终会转发到
InvocationHandler.invoke()方法
4. 代码示例
java
// 1. 定义接口
interface UserService {
void addUser(String name);
}
// 2. 目标类实现接口
class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户:" + name);
}
}
// 3. 实现InvocationHandler
class LogInvocationHandler implements InvocationHandler {
private Object target; // 目标对象
public LogInvocationHandler(Object target) {
this.target = target;
}
// 代理方法调用时触发,proxy:代理实例,method:目标方法,args:方法参数
@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;
}
}
// 4. 生成代理实例并调用
public class JDKProxyDemo {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
// 生成代理实例
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 目标类实现的接口
new LogInvocationHandler(target) // 调用处理器
);
// 调用代理方法
proxy.addUser("张三");
}
}
5. 代理类生成机制
- JDK在运行时通过字节码生成技术动态生成代理类的字节码
- 生成的代理类继承自
Proxy,实现目标接口 - 代理类的每个方法都会调用
InvocationHandler.invoke()方法
二、CGLIB动态代理实现原理
CGLIB(Code Generation Library)是基于继承的动态代理,通过继承目标类并重写其方法来实现增强。
1. 实现条件
- 目标类无需实现接口
- 目标类不能是
final类(无法继承) - 目标方法不能是
final或static(无法重写)
2. 核心组件
Enhancer类 :用于生成代理实例,核心方法create()MethodInterceptor接口 :方法拦截器,通过intercept()方法增强目标方法
3. 实现流程
- 引入CGLIB依赖
- 创建
MethodInterceptor实现类,重写intercept()方法 - 通过
Enhancer.create()生成代理实例 - 调用代理实例的方法,最终会转发到
MethodInterceptor.intercept()方法
4. 代码示例
xml
<!-- Maven依赖 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
java
// 1. 目标类(无需实现接口)
class UserService {
public void addUser(String name) {
System.out.println("添加用户:" + name);
}
}
// 2. 实现MethodInterceptor
class LogMethodInterceptor implements MethodInterceptor {
// o:代理实例,method:目标方法,args:方法参数,methodProxy:代理方法
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 增强逻辑:前置日志
System.out.println("开始调用方法:" + method.getName());
// 调用目标方法(两种方式)
// 方式1:调用目标类的原始方法(可能触发递归)
// Object result = method.invoke(o, args);
// 方式2:调用代理类的fastInvoke方法(推荐,性能更高)
Object result = methodProxy.invokeSuper(o, args);
// 增强逻辑:后置日志
System.out.println("方法调用结束:" + method.getName());
return result;
}
}
// 3. 生成代理实例并调用
public class CGLIBProxyDemo {
public static void main(String[] args) {
// 生成代理实例
UserService proxy = (UserService) Enhancer.create(
UserService.class, // 目标类Class
new LogMethodInterceptor() // 方法拦截器
);
// 调用代理方法
proxy.addUser("李四");
}
}
5. 代理类生成机制
- CGLIB在运行时通过ASM字节码操作库动态生成代理类的字节码
- 生成的代理类继承自目标类,重写目标类的非final方法
- 代理类的每个方法都会调用
MethodInterceptor.intercept()方法
三、JDK动态代理与CGLIB动态代理的详细对比
| 特性 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 实现方式 | 基于接口,代理类实现目标接口 | 基于继承,代理类继承目标类 |
| 目标类要求 | 必须实现至少一个接口 | 无需实现接口,但不能是final类 |
| 代理类生成 | 继承自java.lang.reflect.Proxy |
继承自目标类 |
| 核心API | Proxy.newProxyInstance()、InvocationHandler |
Enhancer.create()、MethodInterceptor |
| 方法增强范围 | 仅增强接口中定义的方法 | 增强目标类的所有非final/非static方法 |
| final方法支持 | 不涉及(接口方法不能是final) | 不支持(final方法无法重写) |
| 性能对比 | - JDK 8以前:性能较差 - JDK 8+:性能优于CGLIB(JDK优化了动态代理实现) | - 创建代理时:性能较差(需要生成字节码) - 运行时:方法调用性能与JDK接近 |
| 生成代理速度 | 快(JDK原生支持) | 慢(需要ASM生成字节码) |
| 内存占用 | 较低 | 较高(生成的代理类字节码较大) |
| 适用场景 | 目标类实现了接口的场景 | 目标类未实现接口的场景 |
| Spring AOP默认选择 | 目标类实现接口时,优先使用JDK动态代理(Spring 5.x+) | 目标类未实现接口时,使用CGLIB |
四、代理选择原则
- 目标类是否实现接口 :
- 实现接口 → 优先使用JDK动态代理(JDK 8+性能更好)
- 未实现接口 → 使用CGLIB动态代理
- 性能需求 :
- 频繁创建代理 → 优先使用JDK动态代理(创建速度快)
- 频繁调用代理方法 → JDK 8+使用JDK动态代理 ,JDK 7-使用CGLIB
- 特殊需求 :
- 需要增强
final方法 → 无法使用动态代理,需考虑其他方案(如AspectJ编译时织入) - 需要代理
static方法 → 动态代理不支持,需考虑其他方案
- 需要增强
五、动态代理的常见应用场景
- Spring AOP:核心实现基于动态代理,根据目标类是否实现接口选择JDK或CGLIB
- 事务管理:通过代理增强方法,自动添加事务的开始、提交、回滚逻辑
- 日志记录:统一记录方法的调用日志、执行时间等
- 权限控制:在方法调用前检查用户权限
- 远程方法调用(RPC):如Dubbo、Feign,通过代理实现远程调用的透明化
- Mock框架:如Mockito,通过动态代理模拟对象行为
总结
JDK动态代理和CGLIB动态代理是Java中实现动态代理的两种主要方式,它们的核心区别在于实现机制 (接口vs继承)和目标类要求。在实际开发中,应根据目标类的特点和性能需求选择合适的代理方式,大多数框架(如Spring AOP)会自动根据目标类的情况选择最优方案。
理解两种动态代理的实现原理和区别,是掌握Java高级特性和框架设计的重要基础,也是面试中的高频考点。