Java动态代理:JDK与CGLIB深度解析
本文讲解Java动态代理,核心是对比JDK(基于接口)和CGLIB(基于继承)这两种主流实现方式的原理、代码与应用场景。
我们来详细、系统地讲解一下Java中的动态代理。
目录
- 什么是代理模式?
- 什么是动态代理? (与静态代理对比)
- 为什么需要动态代理? (应用场景)
- Java中实现动态代理的两种主流方式
- JDK动态代理
- CGLIB动态代理
- 代码实战
- JDK动态代理示例
- CGLIB动态代理示例
- JDK动态代理 vs. CGLIB动态代理 (对比总结)
- 总结
1. 什么是代理模式?
代理模式(Proxy Pattern)是一种设计模式。它的核心思想是:为其他对象提供一种代理以控制对这个对象的访问。
打个比方,你不想亲自处理租房的繁琐事宜(找房、看房、谈价),于是你找了一个房产中介。这个中介就是你的“代理”。
- 你 (Client):调用者。
- 房产中介 (Proxy):代理对象。
- 房东 (Real Subject):被代理的真实对象。
中介不仅能帮你完成租房的核心业务,还可以在此基础上增加一些额外的服务,比如验证房源真伪、提供法律咨询等。这些额外服务就是“增强”功能。
2. 什么是动态代理?
代理模式可以分为 静态代理 和 动态代理。
静态代理:
- 特点:代理类是在编译时就创建好的(
.java文件是程序员手写的)。 - 实现:代理类和被代理类需要实现同一个接口。代理类持有一个被代理对象的引用,并在自己的方法中调用被代理对象的方法,同时可以添加额外逻辑。
- 缺点:非常不灵活。如果接口中新增了一个方法,那么所有的代理类都需要手动修改。如果有很多个类需要被代理,就需要创建同样多的代理类,导致类爆炸。
- 特点:代理类是在编译时就创建好的(
动态代理:
- 特点:代理类是在运行时动态生成的,我们不需要手动编写代理类的代码。
- 优势:非常灵活。可以为一个或多个接口动态地生成一个代理类,解决了静态代理的缺点。
3. 为什么需要动态代理?(应用场景)
动态代理的核心价值在于 在不修改原始类代码的情况下,为其增加额外的功能。这正是面向切面编程(AOP)思想的体现。
常见的应用场景包括:
- 统一日志记录:在方法调用前后记录日志。
- 事务管理:在方法开始前开启事务,在方法结束后根据执行情况提交或回滚事务(Spring的
@Transactional就是这么做的)。 - 性能监控:记录方法的执行时间。
- 权限控制:在方法执行前检查当前用户是否有权限。
- 缓存:在方法执行前检查缓存,如果命中则直接返回,否则执行方法并将结果放入缓存。
4. Java中实现动态代理的两种主流方式
Java生态中主要有两种实现动态代理的方式:JDK自带的动态代理和第三方的CGLIB库。
4.1 JDK动态代理
- 核心:
java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。 - 原理:利用反射机制。在运行时创建一个实现了指定接口列表的代理类。当调用代理对象的方法时,这个调用会被转发到
InvocationHandler的invoke方法中处理。 - 硬性要求:被代理的类必须实现至少一个接口。JDK代理是基于接口的。
4.2 CGLIB动态代理
- 核心:CGLIB是一个强大的、高性能的代码生成库。它被广泛应用于AOP框架(如Spring AOP)中。
- 原理:利用字节码技术(底层是ASM)。它在运行时动态地为目标类创建一个子类作为代理类,并重写父类中的非final方法,从而实现功能的增强。
- 优势:被代理的类可以不实现接口。
- 限制:无法代理被
final修饰的类和方法,因为final类不能被继承,final方法不能被重写。
5. 代码实战
5.1 JDK动态代理示例
步骤:
- 定义一个接口。
- 创建一个实现该接口的真实对象类。
- 创建一个
InvocationHandler实现类,用于编写代理逻辑。 - 使用
Proxy.newProxyInstance()方法创建代理对象。
代码:
1. 定义接口 UserService
public interface UserService {
void addUser(String username);
void deleteUser(String username);
}
2. 实现类 UserServiceImpl
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
System.out.println("数据库操作:添加用户 " + username);
}
@Override
public void deleteUser(String username) {
System.out.println("数据库操作:删除用户 " + username);
}
}
3. 创建 InvocationHandler - MyInvocationHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
// 持有被代理的真实对象
private final Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 在调用真实方法前的增强逻辑
System.out.println("--- [前置增强] 记录日志:准备执行 " + method.getName() + " 方法 ---");
// 2. 调用真实对象的方法 (通过反射)
Object result = method.invoke(target, args);
// 3. 在调用真实方法后的增强逻辑
System.out.println("--- [后置增强] 记录日志:" + method.getName() + " 方法执行完毕 ---");
return result;
}
}
4. 创建代理对象并测试
import java.lang.reflect.Proxy;
public class JdkProxyDemo {
public static void main(String[] args) {
// 1. 创建被代理的真实对象
UserService realUserService = new UserServiceImpl();
// 2. 创建 InvocationHandler,并将真实对象传入
InvocationHandler handler = new MyInvocationHandler(realUserService);
// 3. 使用 Proxy.newProxyInstance() 创建代理对象
// 参数1: 类加载器,用于加载动态生成的代理类
// 参数2: 代理类需要实现的接口数组
// 参数3: 调用处理器
UserService proxyUserService = (UserService) Proxy.newProxyInstance(
realUserService.getClass().getClassLoader(),
realUserService.getClass().getInterfaces(),
handler
);
// 4. 通过代理对象调用方法
System.out.println("代理对象的类型:" + proxyUserService.getClass().getName());
proxyUserService.addUser("Alice");
System.out.println("--------------------");
proxyUserService.deleteUser("Bob");
}
}
输出:
```
代理对象的类型:com.sun.proxy.$Proxy0
--- [前置增强] 记录日志:准备执行 addUser 方法 ---
数据库操作:添加用户 Alice
--- [后置增强] 记录日志:addUser 方法执行完毕 ---
--- [前置增强] 记录日志:准备执行 deleteUser 方法 ---
数据库操作:删除用户 Bob
--- [后置增强] 记录日志:deleteUser 方法执行完毕 ---
#### 5.2 CGLIB动态代理示例
首先,需要添加CGLIB的依赖。
Maven:
```xml
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
步骤:
- 创建一个普通的类(无需实现接口)。
- 创建一个
MethodInterceptor实现类,用于编写代理逻辑。 - 使用
Enhancer类来创建代理对象。
代码:
1. 创建一个普通类 ProductService
public class ProductService {
public void addProduct(String productName) {
System.out.println("数据库操作:添加产品 " + productName);
}
public final void deleteProduct(String productName) {
System.out.println("这是一个 final 方法,无法被CGLIB代理!");
}
}
2. 创建 MethodInterceptor - MyMethodInterceptor
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 1. 前置增强
System.out.println("--- [CGLIB前置增强] 开始事务 ---");
// 2. 调用父类(真实对象)的方法
// 注意:这里推荐使用 proxy.invokeSuper(obj, args) 而不是 method.invoke(target, args)
// 因为 invokeSuper 性能更高,且能避免一些潜在问题。
Object result = proxy.invokeSuper(obj, args);
// 3. 后置增强
System.out.println("--- [CGLIB后置增强] 提交事务 ---");
return result;
}
}
3. 创建代理对象并测试
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyDemo {
public static void main(String[] args) {
// 1. 创建 Enhancer 对象
Enhancer enhancer = new Enhancer();
// 2. 设置父类(被代理的类)
enhancer.setSuperclass(ProductService.class);
// 3. 设置回调(方法拦截器)
enhancer.setCallback(new MyMethodInterceptor());
// 4. 创建代理对象
ProductService proxyProductService = (ProductService) enhancer.create();
// 5. 通过代理对象调用方法
System.out.println("代理对象的类型:" + proxyProductService.getClass().getName());
proxyProductService.addProduct("Laptop");
// 调用 final 方法,不会被代理
System.out.println("--------------------");
proxyProductService.deleteProduct("Mouse");
}
}
输出:
代理对象的类型:com.example.ProductService$$EnhancerByCGLIB$$...
--- [CGLIB前置增强] 开始事务 ---
数据库操作:添加产品 Laptop
--- [CGLIB后置增强] 提交事务 ---
--------------------
这是一个 final 方法,无法被CGLIB代理!
6. JDK动态代理 vs. CGLIB动态代理
| 特性 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 实现基础 | Java 反射机制 | ASM 字节码操作 |
| 代理前提 | 目标类必须实现接口 | 目标类不能是 final 的,否则无法创建子类 |
| 代理方式 | 在运行时动态创建接口的实现类 | 在运行时动态创建目标类的子类 |
| 方法限制 | - | 不能代理 final 或 private 方法,因为子类无法重写它们 |
| 性能 | 在 JDK 1.8 之后,性能与 CGLIB 相差无几,甚至在某些场景下更快。 | 传统上认为性能更高,但现在优势不明显。创建代理对象速度较慢。 |
| 依赖 | JDK 自带,无需额外依赖。 | 需要引入第三方 cglib 库。 |
如何选择?
- 如果目标类实现了接口,或者你推崇面向接口编程,优先使用 JDK 动态代理。
- 如果目标类没有实现接口,而且你又需要代理它,那么就使用 CGLIB。
- 像 Spring AOP 这样的框架会智能选择:如果目标类实现了接口,默认用 JDK 动态代理;如果没实现接口,就用 CGLIB。
7. 总结
动态代理是 Java 中一个非常强大和核心的技术,是理解许多主流框架(如 Spring、Hibernate)内部原理的关键。它允许我们在不侵入原有代码的情况下,对方法进行增强,极大地提高了代码的灵活性和可维护性,是实现 AOP 的基石。掌握 JDK 和 CGLIB 这两种代理方式,能够帮助你更深入地理解和应用 Java。