动态代理(Dynamic Proxy)

CGLIB 代理和 JDK 动态代理是 Java 中实现动态代理的两种主流方式。
Spring AOP 底层就依赖它们来生成代理对象,从而在不修改原始代码的情况下增强方法行为(如日志、事务、权限校验等)。

JDK 动态代理(JDK Dynamic Proxy)

原理

  • 基于 Java 反射机制
  • 只能代理实现了接口的类
  • 在运行时动态生成一个 实现了相同接口的代理类(如 $Proxy0),该代理类持有目标对象的引用,并在调用方法前后插入增强逻辑。

示例

1
2
3
4
5
6
7
8
9
public interface UserService {
void save();
}

public class UserServiceImpl implements UserService {
public void save() {
System.out.println("保存用户");
}
}

JDK 代理会生成一个类似这样的代理类(伪代码):

1
2
3
4
5
6
7
public final class $Proxy0 implements UserService {
private InvocationHandler h;

public void save() {
h.invoke(this, "save", null); // 调用你定义的拦截逻辑
}
}

特点

优点 缺点
纯 JDK 内置,无需额外依赖 只能代理有接口的类
性能较好(尤其在新版本 JVM 中) 无法代理类中未通过接口暴露的方法
安全、稳定

CGLIB 代理(Code Generation Library)

原理

  • 基于 字节码生成技术(运行时动态生成子类)。
  • 不需要目标类实现接口
  • 通过继承目标类,创建一个子类(如 UserServiceImpl$$EnhancerByCGLIB$$xxx),重写其非 final 方法,在方法中插入增强逻辑。

示例

1
2
3
4
5
public class UserService {
public void save() {
System.out.println("保存用户");
}
}

CGLIB 会生成一个子类:

1
2
3
4
5
6
7
public class UserService$$EnhancerByCGLIB extends UserService {
private MethodInterceptor interceptor;

public void save() {
interceptor.intercept(this, "save", ..., super.save()); // 前后可加逻辑
}
}

特点

优点 缺点
可以代理 没有接口的类 不能代理 final 类或 final/private 方法
更灵活,适用范围更广 启动稍慢(需生成字节码)
Spring 默认在无接口时自动使用 需要额外依赖(但 Spring Boot 已内置)

💡 CGLIB 底层使用 ASM 字节码操作库,性能高效。


对比总结

特性 JDK 动态代理 CGLIB 代理
是否需要接口 ✅ 必须实现接口 ❌ 不需要
代理方式 实现接口 继承目标类
能否代理 final 类 ❌(接口不能 final) ❌(无法继承 final 类)
能否代理 final 方法 ✅(只要在接口中) ❌(无法重写 final 方法)
性能(JDK 17+) ⚡ 很快 ⚡ 接近 JDK 代理
依赖 JDK 自带 需 CGLIB(Spring Boot 已包含)
Spring 默认策略 有接口 → JDK
无接口 → CGLIB
可通过 proxyTargetClass=true 强制使用

Spring 中如何选择?

Spring AOP 默认策略:

1
2
3
4
5
if (目标类 implements 接口) {
使用 JDK 动态代理;
} else {
使用 CGLIB 代理;
}

但你可以强制统一使用 CGLIB(推荐,避免因接口问题导致代理失效):

1
@EnableAspectJAutoProxy(proxyTargetClass = true)

实际建议

  • 现代 Spring Boot 项目:直接开启 proxyTargetClass = true,统一使用 CGLIB,避免“部分 Bean 无法被代理”的坑。
  • 不要将 Service 类设计为 final
  • 构造函数中不要调用自身其他方法(CGLIB 代理可能未生效)。

一句话总结

JDK 代理靠接口,CGLIB 代理靠继承;Spring 默认智能选,强制 CGLIB 更省心。