Java 动态代理 CGLIB 与 JDK Proxy 的区别
在 Java 开发中,动态代理是一项非常重要的技术,它允许你在运行时创建代理对象,从而实现方法的增强、拦截和控制。Java 生态中有两种主流的动态代理方案:JDK 自带的 Proxy 和第三方库 CGLIB。理解它们的区别,能帮助你在项目中做出正确的技术选择。
动态代理解决了什么问题?
假设你有一个业务方法,需要在执行前后添加统一的处理逻辑,比如记录日志、权限校验、事务管理、缓存处理等。传统的做法是在每个方法中手动编写这些代码,但这会导致两个严重的问题:
- 代码重复:同样的逻辑散布在多个方法中,维护成本极高
- 违反单一职责:业务方法本该只关注核心逻辑,却被迫掺杂了横切关注点
动态代理的核心思想是:将横切逻辑从业务方法中剥离出来,封装到独立的"代理"对象中。当调用业务方法时,实际上是先执行代理的逻辑,再转发给真实对象。这种模式被称为"AOP"(面向切面编程)的基础。
JDK Proxy 的实现方式
JDK 自带的 java.lang.reflect.Proxy 是最早出现的动态代理方案,它利用 Java 的反射机制,在运行时动态生成代理类。
实现步骤
1. 定义接口
代理类和目标类都实现同一个接口,这是 JDK Proxy 的强制要求。
public interface UserService {
void saveUser(String name);
User getUser(Long id);
}
2. 实现目标类
public class UserServiceImpl implements UserService {
@Override
public void saveUser(String name) {
System.out.println("保存用户: " + name);
}
@Override
public User getUser(Long id) {
System.out.println("查询用户ID: " + id);
return new User(id, "张三");
}
}
3. 实现调用处理器
InvocationHandler 是代理方法调用的核心处理逻辑,所有对代理对象的方法调用都会转发到这里。
public class JdkProxyHandler implements InvocationHandler {
private final Object target;
public JdkProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[JDK Proxy] 方法执行前处理");
Object result = method.invoke(target, args);
System.out.println("[JDK Proxy] 方法执行后处理");
return result;
}
}
4. 创建代理对象
使用 Proxy.newProxyInstance() 方法生成代理实例。
public class JdkProxyDemo {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 接口数组
new JdkProxyHandler(target) // 调用处理器
);
proxy.saveUser("李四");
}
}
运行结果
[JDK Proxy] 方法执行前处理
保存用户: 李四
[JDK Proxy] 方法执行后处理
JDK Proxy 的局限
- 必须基于接口:代理类和目标类必须实现同一个接口,无法代理没有接口的类
- 性能开销:反射调用
method.invoke()相比直接调用有一定性能损耗 - 无法代理 final 类或 final 方法:因为需要继承接口,而 final 类无法被继承
CGLIB 的实现方式
CGLIB(Code Generation Library)是一个强大的高性能代码生成库,它通过继承的方式生成目标类的子类来实现代理,不要求目标类实现任何接口。
引入依赖
如果使用 Maven,需要添加以下依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
实现步骤
1. 定义目标类(无需接口)
public class UserService {
public void saveUser(String name) {
System.out.println("保存用户: " + name);
}
public User getUser(Long id) {
System.out.println("查询用户ID: " + id);
return new User(id, "张三");
}
}
2. 实现方法拦截器
MethodInterceptor 类似于 JDK Proxy 的 InvocationHandler,但在 CGLIB 中功能更强大。
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("[CGLIB] 方法执行前处理");
Object result = proxy.invokeSuper(obj, args);
System.out.println("[CGLIB] 方法执行后处理");
return result;
}
}
3. 创建代理对象
使用 Enhancer 类来生成代理对象。
import net.sf.cglib.proxy.Enhancer;
public class CglibDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class); // 设置父类
enhancer.setCallback(new CglibInterceptor()); // 设置回调
UserService proxy = (UserService) enhancer.create();
proxy.saveUser("王五");
}
}
运行结果
[CGLIB] 方法执行前处理
保存用户: 王五
[CGLIB] 方法执行后处理
CGLIB 的优势
- 无需接口:可以代理任何非 final 的类和方法
- 性能更好:调用
proxy.invokeSuper()比反射调用更快,因为 CGLIB 生成了真正的子类字节码 - 灵活性高:支持多种回调类型,如
FixedValue、InvocationHandler、LazyLoader等
核心区别对比
| 对比维度 | JDK Proxy | CGLIB |
|---|---|---|
| 实现原理 | 基于反射,动态生成实现接口的代理类 | 基于继承,动态生成目标类的子类 |
| 是否需要接口 | 必须实现接口 | 不需要接口 |
| 代理目标 | 只能代理实现了接口的类 | 可以代理任何非 final 类 |
| final 限制 | 无法代理 final 类或 final 方法 | 无法代理 final 类或 final 方法 |
| 性能表现 | 反射调用有一定开销 | 直接调用字节码生成的方法,性能更优 |
| 依赖要求 | JDK 内置,无需额外依赖 | 需要额外引入 CGLIB 库 |
| 代码复杂度 | 稍高,需要定义接口和调用处理器 | 相对简单,直接通过继承实现 |
| 底层 ASM | 不直接使用 ASM | 内部依赖 ASM 操作字节码 |
| 方法拦截 | 通过 InvocationHandler 统一处理 | 通过 MethodInterceptor 可精确控制 |
| Spring 支持 | 默认使用(Spring AOP 基于接口) | 可通过配置启用(用于无接口场景) |
关键差异深度解析
代理生成方式的本质区别
JDK Proxy 采用的是接口实现模式。运行时,JVM 会根据你提供的接口列表,动态创建一个全新的类,这个类实现了目标接口,并将所有方法调用转发给 InvocationHandler。这意味着代理类和目标类在继承关系上完全独立。
CGLIB 采用的是子类继承模式。它在运行时读取目标类的字节码,通过 ASM 库动态生成一个目标类的子类,并重写所有非 final 的方法。代理对象实际上是目标类的子类,方法调用走的是继承链。由于是直接调用父类方法,CGLIB 在执行效率上更有优势。
性能差异的根源
JDK Proxy 的性能瓶颈主要来自两个方面。首先是反射调用开销:method.invoke(target, args) 需要进行方法查找、参数装箱、安全检查等操作,相比直接调用慢了 3 到 10 倍。其次是动态代理类加载开销:每次调用 newProxyInstance 都会生成新的代理类字节码并加载到内存中。
CGLIB 虽然也需要生成字节码,但一旦生成了子类,后续的方法调用就是普通的 Java 方法调用,没有任何反射开销。这也是为什么在性能敏感的场景(如 Spring 的事务管理、MyBatis 的 Mapper 代理)中,CGLIB 往往是更好的选择。
方法拦截能力的不同
JDK Proxy 的 InvocationHandler 有一个特性需要注意:this 指向的是代理对象本身,而不是目标对象。如果在拦截逻辑中调用 method.invoke(this, args) 会导致递归调用代理逻辑,而不是真正执行目标方法。因此必须在 invoke 方法中使用传入的 target 参数。
CGLIB 的 MethodInterceptor 使用 proxy.invokeSuper(obj, args) 来调用目标方法。这里的 obj 是代理对象实例,invokeSuper 会明确调用父类(目标类)的实现,避免代理对象自身的无限递归。
使用场景与选择建议
何时选择 JDK Proxy
当你的项目已经有良好的接口设计,且所有业务类都通过接口暴露时,JDK Proxy 是自然的选择。它是 JDK 原生支持,无需额外依赖,代码结构清晰。
如果你在开发框架或库,需要保持零依赖或最小依赖,JDK Proxy 是更合适的选择。
何时选择 CGLIB
当你的业务类没有实现任何接口,或者你需要代理的类来自第三方库(无法修改源代码添加接口),CGLIB 是唯一的选择。
在性能要求较高的场景中,如高频调用的 RPC 框架、AOP 切面较多的企业应用,CGLIB 的性能优势会更明显。
需要注意的是,Spring 框架在默认情况下会根据目标对象是否实现了接口来决定使用哪种代理方式。如果目标类实现了接口,Spring 会优先使用 JDK Proxy;如果没有实现接口,则会自动切换到 CGLIB。你也可以通过配置强制指定使用某种代理方式。
常见问题与注意事项
为什么代理了却没生效?
如果你发现代理逻辑没有执行,首先要确认代理对象的获取方式是否正确。在某些情况下,Spring 的事务代理可能会因为自我调用(this.xxx())而绕过代理,此时需要通过 AopContext.currentProxy() 获取真正的代理对象来调用方法。
为什么有些方法没有被代理?
检查目标方法是否被声明为 private、static 或 final。这些方法在 CGLIB 中不会被代理,因为 CGLIB 通过继承实现代理,而 private 方法无法在子类中重写,static 方法属于类而非实例,final 方法不允许被子类重写。
构造函数也会被代理吗?
不会。代理类的构造函数实际上是调用目标类的构造函数,不会执行任何额外的拦截逻辑。如果需要在对象创建时执行特殊处理,应该使用工厂模式或在目标类的构造函数中处理。
总结
JDK Proxy 和 CGLIB 都是优秀的动态代理方案,选择哪个取决于你的具体需求。如果你有接口约束,追求代码简洁性,JDK Proxy 完全够用。如果你想突破接口限制,追求更好的性能,CGLIB 是更合适的选择。
理解它们的实现原理和性能差异,能帮助你在架构设计时做出更明智的决策。无论选择哪种方案,动态代理的核心价值在于将横切关注点与业务逻辑分离,让你的代码更清晰、更易于维护。

暂无评论,快来抢沙发吧!