在 Java 开发中,动态代理是一种常见且强大的机制,广泛应用于 AOP、RPC、事务控制等场景。而最常见的两种动态代理方式就是 JDK 动态代理 和 CGLIB 动态代理。本文将带你全面了解它们的原理、区别、性能、Spring 应用方式及适用场景,并通过示例代码深入理解两者的使用方式。
目录
一、动态代理简介
动态代理(Dynamic Proxy)是一种在运行时创建代理类并将调用转发给目标对象的机制。通过它,我们可以在不修改原始代码的情况下,增强方法逻辑,如日志记录、安全检查、事务处理等。
二、JDK 动态代理原理
JDK 动态代理依赖于 反射机制,要求目标类必须实现一个或多个接口。Java 在运行时生成一个实现了这些接口的代理类,调用被转发到 InvocationHandler
的 invoke
方法中。
public interface UserService {
void save();
}
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("保存用户");
}
}
// 创建代理
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy1, method, args) -> {
System.out.println("前置逻辑");
Object result = method.invoke(target, args);
System.out.println("后置逻辑");
return result;
});
三、CGLIB 动态代理原理
CGLIB 采用底层的 ASM 字节码生成技术,为目标类生成一个子类,通过方法拦截器增强方法逻辑。不依赖接口,可直接代理普通类。
public class UserService {
public void save() {
System.out.println("保存用户");
}
}
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("前置逻辑");
Object result = proxy.invokeSuper(obj, args);
System.out.println("后置逻辑");
return result;
});
UserService proxy = (UserService) enhancer.create();
四、核心区别
对比维度 | JDK 动态代理 | CGLIB 动态代理 |
---|---|---|
原理 | 反射 + 接口实现 | 字节码生成子类 |
目标对象 | 必须实现接口 | 不需要接口 |
代理对象 | 实现接口的类 | 继承目标类的子类 |
限制 | 无法代理类 | 无法代理 final 类和 final 方法 |
性能(JDK8+) | 调用速度快,初始化较快 | 初始化稍慢,调用速度相当 |
是否依赖第三方库 | 否(Java 标准库) | 是(cglib + asm) |
五、Spring 中的选择策略
在 Spring AOP 或事务管理中,代理选择的规则如下:
场景 | Spring 默认代理方式 |
---|---|
类实现了接口 | 使用 JDK 动态代理 |
类未实现接口 | 使用 CGLIB |
强制使用 CGLIB | 设置 @EnableAspectJAutoProxy(proxyTargetClass = true) |
实战建议:若希望统一使用 CGLIB(如为了增强 private 方法),可以手动配置开启 CGLIB 模式。
六、示例代码对比
JDK 动态代理
public interface HelloService {
void sayHello();
}
public class HelloServiceImpl implements HelloService {
public void sayHello() {
System.out.println("Hello");
}
}
HelloService proxy = (HelloService) Proxy.newProxyInstance(
HelloServiceImpl.class.getClassLoader(),
new Class[]{HelloService.class},
(proxy1, method, args) -> {
System.out.println("Before");
Object result = method.invoke(new HelloServiceImpl(), args);
System.out.println("After");
return result;
});
proxy.sayHello();
CGLIB 动态代理
public class HelloService {
public void sayHello() {
System.out.println("Hello");
}
}
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("Before");
Object result = proxy.invokeSuper(obj, args);
System.out.println("After");
return result;
});
HelloService proxy = (HelloService) enhancer.create();
proxy.sayHello();
七、总结与建议
-
✅ 如果你的类 实现了接口,优先使用 JDK 动态代理,性能好,依赖少;
-
✅ 如果类 没有接口,只能使用 CGLIB,但注意类和方法不能是
final
; -
✅ 在 Spring 中,两者可共存,Spring 会根据情况自动选择;
-
✅ CGLIB 使用场景更多样,但也更复杂,建议按需使用。
附加说明
-
若需要代理 private 方法,JDK 和 CGLIB 都无法直接实现,需使用更底层的 ASM 或 ByteBuddy;
-
生产环境中推荐使用 Spring AOP 封装的方式,屏蔽代理底层细节。
如果你对这两种代理方式在 Spring AOP、事务管理、权限控制中的应用感兴趣,欢迎关注后续专栏文章 👇
下一篇:Spring AOP 源码剖析:代理模式选择机制揭秘