Soring Java 动态代理:JDK 与 CGLIB 详解
一、为什么需要动态代理?
在上一篇[手写 Spring 事务管理]中,我们在 Service 层手动写了大量事务控制代码:
public void transfer(...) {
try {
transactionManager.beginTransaction(); // 开启事务
// ...业务逻辑...
transactionManager.commit(); // 提交
} catch (Exception e) {
transactionManager.rollback(); // 回滚
} finally {
transactionManager.release(); // 释放
}
}
这套 try-catch 结构和业务代码完全没有关系,但每个 Service 方法都要写一遍,非常冗余。
有没有办法在不修改原有业务代码的前提下,自动给方法加上这些额外逻辑?
答案就是动态代理。
二、什么是动态代理?
先用一个现实例子理解:
早期,电脑厂家自己负责生产、销售和售后三件事,什么都要亲力亲为。后来生意越做越大,厂家决定只专注生产,把销售和售后交给经销商来做。消费者不再直接找厂家,而是找经销商——经销商在厂家的基础上,额外提供了销售和售后服务。
这就是代理的本质:在不改变原有对象的前提下,通过一个代理对象对原有功能进行增强。
动态代理的"动态"体现在:代理对象不是手写的,而是在程序运行期间由 JVM 动态生成的。
Java 中实现动态代理主要有两种方式:基于接口的 JDK 动态代理 和基于子类的 CGLIB 动态代理。
三、JDK 动态代理
3.1 使用前提
被代理的类必须实现至少一个接口,JDK 动态代理通过接口来生成代理对象。
3.2 核心 API
使用 java.lang.reflect.Proxy 类的 newProxyInstance 方法创建代理对象,该方法需要三个参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| ClassLoader | 类加载器 | 和被代理对象使用相同的类加载器,固定写法 |
| Class[] | 接口字节码数组 | 指定代理对象要实现哪些接口,固定写法 |
| InvocationHandler | 处理器 | 在这里编写增强逻辑,每次调用代理方法时触发 |
3.3 代码示例
第一步:定义接口和被代理类
// 接口
public interface ProductDao {
void buyProduct(String name);
}
// 被代理类(厂家)
public class Producer implements ProductDao {
public void buyProduct(String name) {
System.out.println(name + " 售卖了电脑");
}
}
第二步:创建代理对象
public class Consumer {
public static void main(String[] args) {
final Producer producer = new Producer();
// 直接买:调用原始对象
producer.buyProduct("厂家");
// 通过代理买:在原始方法前后可以加入增强逻辑
ProductDao proxy = (ProductDao) Proxy.newProxyInstance(
producer.getClass().getClassLoader(), // 类加载器,固定写法
producer.getClass().getInterfaces(), // 接口数组,固定写法
new InvocationHandler() {
/**
* 每次调用代理对象的方法时,都会进入这里
* @param proxy 当前代理对象的引用(一般不用)
* @param method 当前被调用的方法
* @param args 方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 增强逻辑可以写在这里(前置、后置都可以)
System.out.println("经销商:准备开始销售...");
Object result = method.invoke(producer, args); // 调用原始方法
System.out.println("经销商:售后服务跟进...");
return result;
}
}
);
proxy.buyProduct("经销商");
}
}
输出结果:
厂家 售卖了电脑
经销商:准备开始销售...
经销商 售卖了电脑
经销商:售后服务跟进...
可以看到,代理对象在调用原始方法的前后,各插入了一段逻辑,而 Producer 类的代码完全没有被修改。
3.4 局限性
JDK 动态代理要求被代理类必须实现接口。如果一个普通的 Java 类没有实现任何接口,就无法使用这种方式。
四、CGLIB 动态代理
4.1 使用前提
CGLIB 通过创建被代理类的子类来实现代理,因此被代理类不能用 final 修饰(final 类无法被继承)。
CGLIB 是第三方库,需要引入依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
4.2 核心 API
使用 net.sf.cglib.proxy.Enhancer 类的 create 方法创建代理对象,需要两个参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| Class | 字节码 | 指定被代理类的 Class 对象 |
| Callback | 回调处理器 | 通常传入 MethodInterceptor 实现类,编写增强逻辑 |
4.3 代码示例
被代理类(不需要实现接口)
public class Producer {
public void buyProduct(String name) {
System.out.println(name + " 售卖了电脑");
}
}
创建代理对象
public class Consumer {
public static void main(String[] args) {
final Producer producer = new Producer();
// 直接买
producer.buyProduct("厂家");
// 通过 CGLIB 代理买
Producer proxy = (Producer) Enhancer.create(
Producer.class, // 被代理类的字节码
new MethodInterceptor() { // 方法拦截器
/**
* 每次调用代理对象的方法时触发
* @param o 代理对象的引用
* @param method 被调用的方法
* @param objects 方法参数
* @param methodProxy 方法的代理对象(不常用)
*/
@Override
public Object intercept(Object o, Method method,
Object[] objects, MethodProxy methodProxy)
throws Throwable {
System.out.println("经销商:准备开始销售...");
Object result = method.invoke(producer, objects); // 调用原始方法
System.out.println("经销商:售后服务跟进...");
return result;
}
}
);
proxy.buyProduct("经销商");
}
}
输出结果和 JDK 动态代理一致,区别在于这次 Producer 不需要实现任何接口。
五、JDK vs CGLIB 对比
| 对比项 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 来源 | JDK 官方内置 | 第三方库 |
| 核心类 | Proxy |
Enhancer |
| 实现方式 | 基于接口,生成接口的实现类 | 基于继承,生成被代理类的子类 |
| 被代理类要求 | 必须实现接口 | 不能用 final 修饰 |
| 拦截接口 | InvocationHandler.invoke() |
MethodInterceptor.intercept() |
实际开发中两者经常配合使用:如果被代理类有接口,优先用 JDK 代理;没有接口则用 CGLIB。Spring 框架内部也是根据这个规则自动选择的。
六、动态代理和事务有什么关系?
回到开头的问题:每个 Service 方法都要手写 try-catch 事务控制,太繁琐了。
现在有了动态代理,完全可以用代理对象来做这件事:
- 在调用每个业务方法之前,自动执行
beginTransaction() - 在业务方法正常返回后,自动执行
commit() - 在抛出异常时,自动执行
rollback() - 最终执行
release()
这样业务代码里就不需要写任何事务相关的代码,干净清爽。
这是 Spring AOP 的核心思想, @Transactional 注解背后做的,就是这件事。
更多推荐
所有评论(0)