cglib 与 JDK 动态代理有什么区别?
cglib 和 JDK 动态代理是 Java 中两种主流的代理实现方式,核心区别在于:JDK 动态代理基于接口,要求目标类必须实现接口;cglib 基于继承,通过生成目标类的子类来实现代理,因此可以代理没有实现接口的普通类。 选型时先看目标类是否实现接口——有接口且只需按接口调用,JDK 动态代理足够;没有接口或需要代理类的具体方法,才考虑 cglib。同时要接受 cglib 的继承限制:无法代理 final 类和 final 方法。
两者的工作机制
JDK 动态代理
JDK 自带 java.lang.reflect.Proxy 和 InvocationHandler。运行时为一组接口生成一个实现了这些接口的代理类,方法调用被转发到 InvocationHandler.invoke()。
- 输入:目标类实现的接口列表 + 调用处理器。
- 动作:JVM 在运行时生成代理类字节码。
- 结果:得到一个可强转为接口类型的代理对象,调用接口方法时进入处理器逻辑。
因为代理类实现的是接口,所以只能通过接口类型引用它,目标类自己的非接口方法不在代理范围内。
cglib
cglib(Code Generation Library)是一个字节码生成库,底层依赖 ASM。它为目标类生成一个子类,在子类中覆写父类方法,把调用拦截到 MethodInterceptor.intercept()。
- 输入:目标类(Class 对象)+ 方法拦截器。
- 动作:运行时生成目标类的子类字节码。
- 结果:得到一个目标类的子类实例,调用被覆写的方法时进入拦截逻辑。
由于是继承关系,代理对象可以按目标类类型使用,能覆盖目标类中所有可被继承的方法。
关键差异对比
| 维度 | JDK 动态代理 | cglib |
|---|---|---|
| 实现基础 | 接口 | 继承(生成子类) |
| 目标类要求 | 必须实现至少一个接口 | 无需实现接口,但类和方法不能是 final |
| 可代理范围 | 接口中声明的方法 | 目标类中可被覆写的非 final 方法 |
| 代理对象类型 | 接口类型 | 目标类的子类类型 |
| 依赖 | JDK 内置,无需额外依赖 | 需要引入 cglib(及其 ASM 依赖) |
| 典型限制 | 无法代理没有接口的类 | 无法代理 final 类、final 方法、static 方法、private 方法 |
怎么选
按下面的顺序判断即可:
- 目标类是否实现了接口?
- 是,且调用方只依赖接口 → 优先 JDK 动态代理,零额外依赖,行为可预期。
- 是,但需要代理接口之外的方法 → 考虑 cglib。
- 否 → 只能用 cglib(或其它字节码方案)。
- 目标类或关键方法是否为 final?
- 是 → cglib 无法覆写,需改用接口方案或调整设计。
- 是否愿意引入额外依赖?
- 不想引入 → JDK 动态代理。
- 可以引入 → cglib 适用范围更广。
一个具体场景:你有一个 OrderService 类,没有实现任何接口,想在方法前后加日志。JDK 动态代理做不到,因为它需要一个接口来生成代理类;cglib 可以生成 OrderService 的子类并覆写方法,从而插入日志逻辑。反过来,如果 OrderService 实现了 IOrderService 接口,且调用方都通过接口引用,那么用 JDK 动态代理更简单,也不必引入 cglib。
关于 cglib 的获取
cglib 项目已不再托管在 SourceForge。打开 cglib.sourceforge.net 会被重定向到 GitHub 上的托管地址,源码和发布包都在那里获取。因此在新项目里引用 cglib 时,应使用 GitHub 上维护的版本坐标,而不是旧的 SourceForge 地址。
常见卡点
- 以为 cglib 能代理一切:final 类、final 方法、static 方法、private 方法都无法被覆写,代理会失效或直接报错。
- 用接口类型接收 cglib 代理:cglib 生成的是子类,按接口强转不一定符合预期,注意引用类型。
- 忽略构造器:cglib 通过子类实例化,目标类的构造器会被调用,构造器里的副作用需要考虑。
- 版本与依赖:cglib 依赖 ASM,版本不匹配时可能出现字节码相关的异常,引入时留意依赖树。