21. Spring AOP默认用的是什么动态代理,两者的区别?
大约 3 分钟
Spring AOP 默认使用两种不同的动态代理机制,具体取决于被代理的对象类型:
- JDK 动态代理(JDK Dynamic Proxy)
- CGLIB 动态代理(CGLIB Proxy)
1. JDK 动态代理
工作机制:
- JDK 动态代理基于 Java 的接口机制。它要求目标类必须实现至少一个接口。代理对象是基于接口的,因此代理类与目标类实现相同的接口,并在运行时生成代理实例。
- JDK 动态代理使用
java.lang.reflect.Proxy
类和InvocationHandler
接口来动态创建代理对象。代理对象会将方法调用委托给InvocationHandler
的invoke()
方法。
优点:
- 直接利用 JDK 提供的功能,无需额外的第三方库。
- 生成的代理类实现了目标类的接口,结构清晰。
缺点:
- 只能代理实现了接口的类。如果类没有实现接口,就不能使用 JDK 动态代理。
示例:
public interface MyService {
void doSomething();
}
public class MyServiceImpl implements MyService {
public void doSomething() {
System.out.println("Doing something...");
}
}
// 动态代理的实现
MyService proxy = (MyService) Proxy.newProxyInstance(
MyService.class.getClassLoader(),
new Class[]{MyService.class},
(proxy, method, args) -> {
System.out.println("Before method");
Object result = method.invoke(new MyServiceImpl(), args);
System.out.println("After method");
return result;
});
proxy.doSomething();
2. CGLIB 动态代理
工作机制:
- CGLIB(Code Generation Library)基于继承机制生成目标类的子类。与 JDK 动态代理不同,CGLIB 不依赖接口,而是通过创建目标类的子类并重写其方法来实现代理。
- CGLIB 通过字节码操作生成代理类,在运行时创建目标类的子类,并拦截方法调用。
优点:
- 能代理没有实现接口的类,适用于任何普通类。
- 在不使用接口时,CGLIB 是一个较好的选择,因为它能代理目标类的所有方法。
缺点:
- 需要额外的 CGLIB 库支持,并且代理类需要继承自目标类,因此目标类或方法不能是
final
。 - 由于基于字节码操作,性能可能稍逊于 JDK 动态代理,尤其在创建代理对象时。
示例:
public class MyService {
public void doSomething() {
System.out.println("Doing something...");
}
}
// 使用 CGLIB 创建代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MyService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("Before method");
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method");
return result;
});
MyService proxy = (MyService) enhancer.create();
proxy.doSomething();
Spring AOP 默认的代理选择策略
- 接口存在时:如果目标类实现了接口,Spring AOP 默认使用 JDK 动态代理。
- 无接口时:如果目标类没有实现接口,Spring AOP 会自动切换到 CGLIB 动态代理。
两者的区别总结
- 代理方式:JDK 动态代理基于接口,CGLIB 动态代理基于类的子类。
- 性能:JDK 动态代理在接口代理情况下性能较好,而 CGLIB 在需要代理没有接口的类时更有效,但生成代理类的过程稍慢。
- 使用场景:JDK 动态代理适用于所有实现接口的情况,而 CGLIB 可以代理那些没有实现接口的普通类。
在实际开发中,建议尽量使用接口,并依赖 JDK 动态代理,只有在需要代理非接口的类时才使用 CGLIB 动态代理。