Spring AOP默认采用的动态代理技术是什么,二者有何差异?
在 Spring AOP 中,默认使用两种代理方式来增强对象的功能:
JDK 动态代理:这种方式适用于目标类实现了 接口 的情况。代理对象是通过接口来创建的,代理的工作是基于接口的。
CGLIB 动态代理:在 Spring Boot 2.x 版本中,默认使用这种方式。它是通过 继承 目标类来创建一个子类进行代理,适合那些 没有接口 的类。
简单来说,JDK 动态代理 依赖接口,而 CGLIB 动态代理 则通过类的继承来创建代理。
📚 知识内容
🔍 JDK 动态代理
JDK 动态代理的核心是 Java 的反射机制,它通过 java.lang.reflect.Proxy 类生成代理对象,代理对象必须实现目标对象的接口。JDK 动态代理代理接口中的方法,通过 InvocationHandler 的 invoke() 方法来处理增强逻辑。
工作原理:
代理接口:代理类和目标类必须实现同一个接口。
代理创建:通过 Proxy.newProxyInstance() 动态生成代理类,并把目标类和增强逻辑传入。
方法调用:当代理类调用目标方法时,InvocationHandler 的 invoke() 方法被触发,执行增强逻辑(例如记录日志、权限校验等),然后再调用目标方法。
适用场景:
必须使用接口才能代理的情况。
对目标方法的增强操作,特别是接口方法。
示例代码:
interface Service {
void performAction();
}
class ServiceImpl implements Service {
@Override
public void performAction() {
System.out.println(“Performing action in ServiceImpl”);
}
}
class ServiceInvocationHandler implements InvocationHandler {
private final Object target;
public ServiceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method execution");
Object result = method.invoke(target, args);
System.out.println("After method execution");
return result;
}
}
public class JDKProxyDemo {
public static void main(String[] args) {
Service service = new ServiceImpl();
Service proxy = (Service) Proxy.newProxyInstance(
service.getClass().getClassLoader(),
service.getClass().getInterfaces(),
new ServiceInvocationHandler(service)
);
proxy.performAction();
}
}
🔨 CGLIB 动态代理
CGLIB 动态代理通过继承目标类,创建代理类的子类,并在其中重写目标方法实现代理。CGLIB 不依赖于接口,可以代理没有接口的类。
工作原理:
字节码增强:通过字节码生成工具(如 ASM)在运行时创建目标类的子类。
方法重写:生成的代理类重写目标类的方法,插入增强逻辑。
方法调用:当调用代理类中的方法时,先执行增强逻辑(例如,记录日志),然后调用目标方法。
适用场景:
没有接口的类,或者需要对类中方法的增强。
目标类没有实现接口时,CGLIB 可以代替 JDK 动态代理。
示例代码:
class Service {
public void performAction() {
System.out.println(“Performing action in Service”);
}
}
class ServiceMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(“Before method execution”);
Object result = proxy.invokeSuper(obj, args);
System.out.println(“After method execution”);
return result;
}
}
public class CGLIBProxyDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Service.class);
enhancer.setCallback(new ServiceMethodInterceptor());
Service proxy = (Service) enhancer.create();
proxy.performAction();
}
}
🔄 为什么 Spring Boot 2.x 默认使用 CGLIB?
Spring Boot 2.x 默认使用 CGLIB 动态代理,主要原因是:
接口要求的限制:JDK 动态代理要求目标类必须实现接口。如果目标类没有接口,JDK 动态代理会失败,这对于没有接口的类显得不太友好。
CGLIB 提供了更多灵活性:CGLIB 通过继承目标类生成代理,不需要接口,因此可以更好地处理没有接口的类。
没有显著的性能差异:虽然 JDK 动态代理和 CGLIB 在某些情况下存在性能差异,但这种差异通常并不显著,而且 CGLIB 被直接集成在 Spring 中,减少了外部依赖的麻烦。
🌱 知识拓展
🧠 JDK 动态代理 vs CGLIB 动态代理
特性 JDK 动态代理 CGLIB 动态代理
代理方式 基于接口,代理对象实现目标接口 基于继承,生成目标类的子类
接口要求 需要目标类实现接口 不需要接口,直接继承目标类
方法增强 只能增强接口方法 可以增强类中任何方法(不能增强 final 方法)
不能代理的类 无法代理 final 类和 final 方法 无法代理 final 类和 final 方法
性能 性能较低,依赖反射机制 性能较高,直接生成子类进行方法拦截
适用场景 目标类必须实现接口,增强接口中的方法 适用于没有接口的类或增强类中的方法
⚡ 性能对比
根据不同的 JDK 版本,JDK 动态代理与 CGLIB 在性能上可能存在差异:
JDK 6:在方法调用次数较少时,JDK 动态代理和 CGLIB 性能差距不大。
JDK 7:在运行次数较少时,JDK 动态代理反而更快,随着调用次数增加,CGLIB 的性能略优。
JDK 8:性能差距较小,两者表现接近。
总体而言,JDK 动态代理在运行时的性能较低,CGLIB则在频繁调用时可能表现更好。
🔄 总结
JDK 动态代理适用于代理实现了接口的类,尤其是接口方法的增强。它的优势是通过反射来创建代理类,但也有接口限制和性能开销。
CGLIB 动态代理适用于没有接口的类,通过继承生成子类来实现代理,性能上较为优秀,但不支持 final 类和方法的代理。
Spring 默认使用 CGLIB,以避免 JDK 动态代理对接口的依赖,使得代理更加灵活。如果你不需要接口代理,可以在配置中手动切换到 JDK 动态代理。
