小易说IT 小易说IT

CGLIB 动态代理 vs JDK 动态代理 核心区别

一句话总览:JDK 动态代理基于接口;CGLIB 基于继承子类,Spring AOP 默认优先 JDK,目标无接口时自动切 CGLIB(SpringBoot 2.x + 可配置强制 CGLIB)

对比表

对比项

JDK 动态代理

CGLIB(Code Generation Library)

底层原理

实现目标类的接口,生成代理类

继承目标类,生成它的子类作为代理

依赖

JDK 原生,java.lang.reflect.Proxy,无额外包

ASM 字节码框架生成字节码;早期需要引入 cglib 包,Spring 内置 repackaged cglib

目标类限制

必须实现至少一个接口

目标类不能是 final;被代理方法不能是 final /private(无法被子类重写)

代理对象类型

代理类和目标类实现同一个接口,代理对象是接口类型

代理对象是目标类的子类实例,可以强转为目标类本身

回调入口

InvocationHandler.invoke()

MethodInterceptor.intercept()

方法调用逻辑

只拦截接口中声明的方法

拦截所有可重写的非 final 方法

性能特点

JDK8 + 优化很好,调用开销低;生成代理类慢一点

老版本 CGLIB 创建代理慢(字节码生成),方法调用快;现代版本差距缩小

Spring AOP 默认策略

优先使用;目标有接口就 JDK

目标没有实现任何接口时,自动使用 CGLIB

关键细节

1. JDK 动态代理示例要点

// 接口
public interface UserService {
    void addUser();
}
// 目标实现类
class UserServiceImpl implements UserService {
    @Override
    public void addUser() {}
}
// 代理逻辑:InvocationHandler
InvocationHandler handler = (proxy, method, args) -> {
    // 前置增强:权限校验、日志、安全鉴权
    System.out.println("JDK代理前置");
    Object res = method.invoke(target, args);
    System.out.println("JDK代理后置");
    return res;
};
// 创建代理
UserService proxy = (UserService) Proxy.newProxyInstance(
        target.getClass().getClassLoader(),
        target.getClass().getInterfaces(),
        handler
);

✅ 只能代理接口方法;你直接调用实现类里不属于接口的方法,代理拦截不到。

2. CGLIB 代理示例要点

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, methodProxy) -> {
    System.out.println("CGLIB前置增强");
    // 调用父类原方法(重点:methodProxy.invokeSuper,不要method.invoke)
    Object res = methodProxy.invokeSuper(obj, args);
    System.out.println("CGLIB后置增强");
    return res;
});
UserServiceImpl proxy = (UserServiceImpl) enhancer.create();

⚠️ 坑:

  • final class、final method 无法拦截

  • private 方法不能被子类重写,不会进入 intercept

  • 不要写 method.invoke(target, args) 会绕过 CGLIB 子类,直接调用原对象,失去代理效果

和你上一个问题【动态代理增强安全性】结合

安全增强场景(鉴权、参数校验、防篡改、审计日志、权限拦截):

  1. 如果业务层统一面向接口编程:优先 JDK 代理,轻量、无第三方依赖

  2. 遗留代码 / 第三方 SDK 类,没有接口:只能用 CGLIB

  3. 注意:CGLIB 不能代理 final 类,很多安全框架、工具类是 final,这时方案要改成装饰器模式

Spring AOP 配置补充(SpringBoot3 / Java17)

Java17 强模块化 + 封镜(encapsulation),CGLIB/ASM 会碰到 illegal reflective access:

# 强制Spring使用CGLIB代理(默认:目标有接口用JDK)
spring.aop.proxy-target-class=true

Java17 环境下使用 CGLIB,部分场景需要 JVM 参数开放模块权限,生产上如果可控,优先面向接口 + JDK 代理更稳。


本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。

原文链接 https://www.yijunzhao.cc/archives/cglib-dynamic-proxy-vs-jdk-dynamic-proxy-core-differences

欢迎访问 https://www.yijunzhao.cc/

https://www.yijunzhao.cc/