Java 动态代理是实现面向切面编程(AOP)的关键技术之一。Java 中最常用的两种动态代理机制是 JDK 动态代理(基于接口)和 CGLIB 代理(基于继承)。虽然它们都能实现代理功能,但由于底层字节码生成机制的根本差异,导致了它们在性能特性上有着显著的区别。
1. JDK 动态代理:基于反射与接口
JDK 动态代理依赖于 java.lang.reflect.Proxy,它要求被代理的类必须实现一个或多个接口。其字节码生成过程如下:
- 字节码生成: 在运行时,JDK 会生成一个全新的代理类(通常命名为 $ProxyN,如 $Proxy0)。这个代理类实现了目标接口,并且继承了 java.lang.reflect.Proxy 类。
- 方法调用: 代理类中的所有方法实现,都会将调用转发给传入的 InvocationHandler 的 invoke 方法。
- 性能瓶颈: invoke 方法本身是通过 Java 的反射机制 (Method.invoke()) 来调用实际目标对象的方法。反射调用会带来额外的开销(方法查找、安全检查、参数装箱/拆箱等)。
总结: JDK 代理的优势在于生成字节码的速度快(因为结构简单),但运行时由于大量使用反射,执行效率相对较低。
2. CGLIB 代理:基于继承与字节码增强
CGLIB (Code Generation Library) 允许代理那些没有实现接口的类。它通过修改字节码的方式来实现代理,使用了底层的 ASM 库。
- 字节码生成: CGLIB 会生成目标类的子类。它重写(Override)了父类中的所有非 final 方法。
- 方法调用: 在子类重写的方法中,CGLIB 注入了逻辑,将调用转发给用户定义的 MethodInterceptor 的 intercept 方法。
- 性能优势: intercept 方法可以直接调用父类的原始方法(通过特殊的指令,如 super.method()),避免了反射的开销。
总结: CGLIB 的字节码生成过程相对复杂,初始加载速度略慢。但一旦代理类创建完成,后续的方法调用是直接的方法调用(不是反射),因此在长期运行和高并发场景下,运行时性能通常优于 JDK 代理。
3. 实操对比:性能测试示例
我们通过一个简单的测试来量化 JDK 代理和 CGLIB 代理的性能差异。我们将进行 1000 万次方法调用。
目标接口与实现
interface Service {
void execute();
}
class ServiceImpl implements Service {
@Override
public void execute() {
// 模拟实际业务逻辑
}
}
JDK 动态代理实现
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// JDK 代理处理器
class JdkInvocationHandler implements InvocationHandler {
private final Object target;
public JdkInvocationHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// System.out.println("JDK Before");
return method.invoke(target, args);
}
}
public static Service getJdkProxy(Service target) {
return (Service) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new JdkInvocationHandler(target)
);
}
CGLIB 动态代理实现
(注意:需要引入 CGLIB 库,如 cglib-3.3.0.jar)
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// CGLIB 方法拦截器
class CglibMethodInterceptor implements MethodInterceptor {
private final Object target;
public CglibMethodInterceptor(Object target) { this.target = target; }
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// System.out.println("CGLIB Before");
// 使用 MethodProxy 比使用反射 Method.invoke() 更快
return proxy.invoke(target, args);
}
}
public static Service getCglibProxy(Service target) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new CglibMethodInterceptor(target));
return (Service) enhancer.create();
}
性能测试主函数
public class ProxyPerformanceTest {
private static final int ITERATIONS = 10000000; // 1000万次调用
public static void main(String[] args) {
ServiceImpl target = new ServiceImpl();
// 1. JDK Proxy Setup & Test
Service jdkProxy = getJdkProxy(target);
long jdkStart = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
jdkProxy.execute();
}
long jdkEnd = System.currentTimeMillis();
System.out.println("JDK Proxy Time: " + (jdkEnd - jdkStart) + " ms");
// 2. CGLIB Proxy Setup & Test
Service cglibProxy = getCglibProxy(target);
long cglibStart = System.currentTimeMillis();
for (int i = 0 < ITERATIONS; i++) {
cglibProxy.execute();
}
long cglibEnd = System.currentTimeMillis();
System.out.println("CGLIB Proxy Time: " + (cglibEnd - cglibStart) + " ms");
// 3. Direct Call (Baseline)
long directStart = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
target.execute();
}
long directEnd = System.currentTimeMillis();
System.out.println("Direct Call Time: " + (directEnd - directStart) + " ms");
}
}
运行结果分析 (典型结果):
| 调用类型 | 时间消耗 (ms) |
|---|---|
| Direct Call | 10 |
| CGLIB Proxy | 150 – 250 |
| JDK Proxy | 300 – 500 |
结论
通过性能对比可以看出,CGLIB 代理机制(基于字节码增强实现的直接方法调用)在运行时效率上明显优于 JDK 动态代理(基于反射调用)。在 Spring AOP 等框架中,如果目标类实现了接口,通常优先使用 JDK 代理(因为生成速度快且无需依赖第三方库);但如果目标类未实现接口,则必须使用 CGLIB。对于追求极致性能的高并发场景,CGLIB 的运行时优势更为显著。
汤不热吧