Proxy只能代理接口,不能代理类;CGLIB可代理类但要求类与方法非final。Proxy通过实现接口生成代理,CGLIB通过继承重写非final方法实现。

Proxy动态代理只能代理接口,不能代理类
Java原生的 java.lang.reflect.Proxy 生成的是接口实现类,它在运行时创建一个实现了指定接口的代理对象。如果你传入的是一个具体类(比如 UserService),Proxy.newProxyInstance() 会直接抛出 IllegalArgumentException:“class is not an interface”。这是最常踩的坑——误以为 Proxy 能像 CGLIB 那样“代理任意类”。
实际使用中,必须确保:
- 目标对象有明确实现的接口
- Proxy.newProxyInstance() 的第二个参数是 Class>[] 类型的接口数组,不能传 target.getClass()
- 代理逻辑写在 InvocationHandler 的 invoke() 方法里,注意 method.invoke(target, args) 可能抛出受检异常,需包装为运行时异常或显式处理
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(), // ✅ 必须是接口数组
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object result = method.invoke(target, args); // ⚠️ 注意异常类型
System.out.println("after");
return result;
}
}
);CGLIB代理不需要接口,但要求类和方法不是final
CGLIB(通常通过 net.sf.cglib.proxy.Enhancer)是在运行时继承目标类、重写非 final 方法来实现代理。这意味着:
- 目标类不能是 final(否则 Enhancer.create() 报 IllegalAccessError 或 CodeGenerationException)
- 要被拦截的方法也不能是 final 或 static(否则无法重写,代理失效)
- 构造函数会被调用两次:一次是 CGLIB 创建子类实例,一次是原始类初始化(若含副作用需留意)
Spring AOP 默认策略是:有接口走 JDK Proxy,无接口才 fallback 到 CGLIB。但 Spring Boot 2.0+ 默认已启用 CGLIB 代理(spring.aop.proxy-target-class=true),所以即使有接口也可能走 CGLIB——这点容易被忽略。
两者对构造器、私有方法、静态方法的处理完全不同
Proxy 完全不涉及构造过程,它只管接口方法调用;CGLIB 会调用目标类的构造器(默认调用无参构造),如果目标类没有无参构造且未配置 setCallbacksFilter 或自定义 Callback,就会报 InstantiationException。
关于方法可见性:
- Proxy 只能拦截接口中声明的公共方法(public),接口外的方法(包括 private、protected、包级)完全不可见
- CGLIB 可以拦截类中所有非 final 的 public/protected 方法,但依然无法拦截 private 和 static 方法(字节码层面不可覆写)
常见误判场景:
- 在 InvocationHandler 里试图通过 method.getName() 拦截 toString() 或 hashCode()?这些是 Object 方法,只有当接口显式继承了它们(如 public interface Service extends Comparable)才会被代理到
- 用 CGLIB 代理一个含 @PostConstruct 方法的类?该方法在父类构造后执行,但 CGLIB 子类可能干扰其触发时机
性能与字节码生成行为差异明显
Proxy 第一次生成代理类较慢(需生成字节码 + 加载类),但后续复用快;CGLIB 首次生成子类更重(要分析整个类结构、生成继承类、处理桥接方法等),尤其在大量代理不同类时容易引发 OutOfMemoryError: Metaspace。
立即学习“Java免费学习笔记(深入)”;
关键区别点:
- Proxy 生成的类名形如 $Proxy0,位于 sun.misc.ProxyGenerator 控制下,不可序列化(除非手动实现 writeObject)
- CGLIB 生成的类名形如 UserService$$EnhancerByCGLIB$$a1b2c3d4,默认可序列化(但需确保回调对象也支持)
- CGLIB 依赖 ASM 直接操作字节码,对 Java 版本敏感(新版 CGLIB 3.3+ 支持 Java 17,旧版在 Java 11+ 可能失败)
线上排查建议:
- 开启 JVM 参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 查看 Proxy 生成的 class 文件
- 对 CGLIB,加 -Dcglib.debugLocation=/tmp/cglib 输出生成的类文件
- 若发现代理对象无法被 Jackson 序列化,大概率是 CGLIB 代理类的字段被设为 transient 或缺少无参构造
Proxy 和 CGLIB 的根本分歧不在“哪个更好”,而在于“谁更适合当前约束”:有没有接口、类是否 final、方法是否可覆写、是否需要序列化、JVM 环境是否受限——这些细节一旦错配,问题往往出现在运行时,而不是编译期。










