4.3 AOP:动态代理的替身文学(JDK vs CGLIB)
引言:AOP的替身文学
在Spring的世界里,AOP(面向切面编程)就像是一场替身文学大戏。你的业务代码是主角,而AOP则是那个默默无闻的替身演员,负责处理那些“脏活累活”,比如日志记录、事务管理、权限校验等。而这场替身文学的主角,就是动态代理。Spring提供了两种替身演员:JDK动态代理和CGLIB动态代理。今天我们就来揭秘这场替身文学的幕后故事。
1. JDK动态代理:接口的忠实替身
1.1 什么是JDK动态代理? JDK动态代理是基于接口的代理模式。它要求被代理的类必须实现至少一个接口。Spring会为这个接口生成一个代理类,所有的调用都会通过这个代理类来执行。
1.2 核心原理
- InvocationHandler:这是JDK动态代理的核心接口。你需要实现它的
invoke方法,在这里定义代理逻辑。 - Proxy.newProxyInstance:这是生成代理对象的方法。
1.3 代码示例
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 1. 定义接口
interface UserService {
void saveUser();
}
// 2. 实现接口
class UserServiceImpl implements UserService {
@Override
public void saveUser() {
System.out.println("保存用户信息");
}
}
// 3. 实现InvocationHandler
class UserServiceProxy implements InvocationHandler {
private Object target;
public UserServiceProxy(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;
}
}
// 4. 测试JDK动态代理
public class JdkProxyDemo {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new UserServiceProxy(userService)
);
proxy.saveUser();
}
}
1.4 运行结果
前置通知:开始保存用户
保存用户信息
后置通知:用户保存成功
1.5 优缺点
- 优点:基于接口,符合Java的设计原则。
- 缺点:只能代理实现了接口的类。
2. CGLIB动态代理:类的万能替身
2.1 什么是CGLIB动态代理? CGLIB(Code Generation Library)是一个强大的代码生成库,它可以在运行时动态生成类的子类,从而实现对类的代理。与JDK动态代理不同,CGLIB不需要接口,直接对类进行代理。
2.2 核心原理
- MethodInterceptor:这是CGLIB的核心接口。你需要实现它的
intercept方法,在这里定义代理逻辑。 - Enhancer:这是生成代理对象的工具类。
2.3 代码示例
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 1. 定义目标类
class OrderService {
public void createOrder() {
System.out.println("创建订单");
}
}
// 2. 实现MethodInterceptor
class OrderServiceInterceptor 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;
}
}
// 3. 测试CGLIB动态代理
public class CglibProxyDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class);
enhancer.setCallback(new OrderServiceInterceptor());
OrderService proxy = (OrderService) enhancer.create();
proxy.createOrder();
}
}
2.4 运行结果
前置通知:开始创建订单
创建订单
后置通知:订单创建成功
2.5 优缺点
- 优点:不需要接口,可以直接代理类。
- 缺点:生成代理类的速度较慢,且无法代理
final类或final方法。
3. JDK vs CGLIB:替身演员的终极对决
| 特性 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 代理方式 | 基于接口 | 基于类 |
| 性能 | 较快 | 较慢(生成子类) |
| 限制 | 只能代理实现了接口的类 | 无法代理final类或final方法 |
| 适用场景 | 适合代理接口 | 适合代理类 |
4. Spring的选择:替身演员的幕后导演
Spring在默认情况下会根据目标类是否实现了接口来选择使用JDK动态代理还是CGLIB动态代理:
- 如果目标类实现了接口,Spring会优先使用JDK动态代理。
- 如果目标类没有实现接口,Spring会使用CGLIB动态代理。
你也可以通过配置强制使用CGLIB:
<aop:config proxy-target-class="true">
<!-- AOP配置 -->
</aop:config>
5. 图文并茂:替身文学的全景图
为了更直观地理解JDK动态代理和CGLIB动态代理的工作原理,我们绘制一张动态代理的全景图。这张图将展示两种代理模式的核心流程和区别。
AOP动态代理全景图
graph TD
A[客户端] --> B[JDK动态代理]
A --> C[CGLIB动态代理]
subgraph JDK动态代理
B --> D[Proxy.newProxyInstance]
D --> E[InvocationHandler]
E --> F[目标对象]
F --> G[接口方法调用]
end
subgraph CGLIB动态代理
C --> H[Enhancer.create]
H --> I[MethodInterceptor]
I --> J[目标对象]
J --> K[类方法调用]
end
style B fill:#f9f,stroke:#333,stroke-width:2px
style C fill:#bbf,stroke:#333,stroke-width:2px
style D fill:#f96,stroke:#333,stroke-width:2px
style E fill:#f96,stroke:#333,stroke-width:2px
style H fill:#6f9,stroke:#333,stroke-width:2px
style I fill:#6f9,stroke:#333,stroke-width:2px
图例说明
-
JDK动态代理流程:
- 客户端调用代理对象。
- 代理对象通过
Proxy.newProxyInstance生成。 - 调用逻辑由
InvocationHandler处理。 - 最终调用目标对象的接口方法。
-
CGLIB动态代理流程:
- 客户端调用代理对象。
- 代理对象通过
Enhancer.create生成。 - 调用逻辑由
MethodInterceptor处理。 - 最终调用目标对象的类方法。
-
颜色区分:
- 紫色:JDK动态代理相关组件。
- 蓝色:CGLIB动态代理相关组件。
- 橙色:JDK动态代理的核心类。
- 绿色:CGLIB动态代理的核心类。
6. 常见面试题
6.1 JDK动态代理和CGLIB动态代理的区别是什么?
- JDK动态代理基于接口,CGLIB基于类。
- JDK动态代理性能较好,CGLIB生成代理类较慢。
- JDK动态代理只能代理实现了接口的类,CGLIB可以代理普通类,但不能代理
final类或final方法。
6.2 Spring AOP默认使用哪种动态代理?
- Spring AOP默认使用JDK动态代理,如果目标类没有实现接口,则使用CGLIB动态代理。
6.3 如何强制Spring使用CGLIB动态代理?
- 在Spring配置中设置
proxy-target-class="true"。
6.4 CGLIB动态代理的原理是什么?
- CGLIB通过生成目标类的子类来实现代理,重写父类的方法,并在子类中插入代理逻辑。
6.5 JDK动态代理的InvocationHandler接口的作用是什么?
InvocationHandler接口用于定义代理逻辑,所有的代理方法调用都会通过invoke方法来处理。
7. 总结:替身文学的终极奥义
- JDK动态代理:适合代理接口,性能较好,但限制较多。
- CGLIB动态代理:适合代理类,功能强大,但性能稍差。
无论选择哪种替身演员,AOP都能让你的代码更加优雅,业务逻辑更加清晰。下次面试官问你AOP的原理时,你可以自信地说:“AOP就是一场替身文学,而Spring是这场戏的幕后导演!”