JDK动态代理和CGLIB动态代理
下面是对 JDK动态代理 和 CGLIB动态代理 的详细解释、关键区别,以及它们在 Spring框架 中的应用。
一、JDK动态代理(JDK Dynamic Proxy)
1. 原理
JDK动态代理是Java标准库(java.lang.reflect.Proxy)提供的机制,它通过接口来实现代理。其核心是:
- 在运行时,动态生成一个实现了指定接口的代理类。
- 该代理类继承自
java.lang.reflect.Proxy,并实现你指定的接口列表。 - 所有接口方法的调用都会被转发到
InvocationHandler的invoke()方法中。
2. 实现步骤
// 1. 定义接口
public interface UserService {
void save();
void delete();
}
// 2. 实现类
public class UserServiceImpl implements UserService {
public void save() { System.out.println("保存用户"); }
public void delete() { System.out.println("删除用户"); }
}
// 3. 创建InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增强:在方法执行前");
Object result = method.invoke(target, args); // 调用原始方法
System.out.println("后置增强:在方法执行后");
return result;
}
}
// 4. 创建代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new MyInvocationHandler(new UserServiceImpl())
);
proxy.save(); // 输出增强日志
3. 优点
- 纯Java标准库,无需额外依赖。
- 类型安全,编译期检查接口方法。
- 性能较好,适用于接口代理场景。
4. 缺点
- 必须基于接口:目标类必须实现至少一个接口,否则无法使用。
- 无法代理没有接口的类。
二、CGLIB动态代理(Code Generation Library)
1. 原理
CGLIB(Code Generation Library)是一个第三方字节码操作库(基于ASM),它通过继承目标类来实现代理。
- 在运行时,动态生成目标类的子类。
- 重写目标类中的非final方法,插入增强逻辑(如事务、日志)。
- 使用
MethodInterceptor拦截方法调用。
2. 实现步骤
// 1. 目标类(不需要实现接口)
public class UserService {
public void save() { System.out.println("保存用户"); }
public void delete() { System.out.println("删除用户"); }
}
// 2. 实现MethodInterceptor
public class MyMethodInterceptor 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 enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class); // 设置父类
enhancer.setCallback(new MyMethodInterceptor());
UserService proxy = (UserService) enhancer.create();
proxy.save(); // 输出增强日志
⚠️ 注意:CGLIB 不能代理
final类或final方法,因为无法生成子类覆盖。
3. 优点
- 无需接口:可以代理任意非final类。
- 功能强大,支持类级别的代理。
- Spring中默认用于没有接口的类。
4. 缺点
- 依赖第三方库(cglib-nodep 或 asm)。
- 性能略低于JDK代理(字节码生成和类加载有开销)。
- 无法代理
final类/方法。 - 生成的子类可能影响继承关系(如
getClass()返回的是代理类,不是原始类)。
三、JDK动态代理 vs CGLIB动态代理:关键区别对比
| 特性 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 代理依据 | 接口(Interface) | 类(Class) |
| 代理方式 | 实现接口,组合调用 | 继承目标类,重写方法 |
| 是否需要接口 | ✅ 必须 | ❌ 不需要 |
| 代理目标限制 | 不能代理类,只能代理接口 | 不能代理 final 类或 final 方法 |
| 性能 | 较高(反射调用,但JVM优化好) | 略低(字节码生成+方法拦截) |
| 依赖 | Java标准库(无依赖) | 需要引入 CGLIB / ASM 库 |
| 代理类生成方式 | Proxy.newProxyInstance() |
Enhancer.create() |
| 方法调用方式 | Method.invoke() |
MethodProxy.invokeSuper() |
| 适用场景 | 接口明确、标准Java开发 | 无接口的第三方类、Spring AOP默认备选 |
✅ 总结一句话:
JDK代理是“面向接口”,CGLIB是“面向类”。
四、在Spring中的应用
Spring AOP(面向切面编程)底层就使用了这两种代理机制,自动选择策略如下:
1. Spring AOP代理选择规则(Spring 5+)
| 目标对象情况 | Spring 选择的代理方式 |
|---|---|
| 实现了至少一个接口 | ✅ JDK动态代理(优先) |
| 没有实现任何接口 | ✅ CGLIB动态代理 |
配置了 proxyTargetClass = true |
✅ 强制使用 CGLIB(即使有接口) |
2. 如何配置代理方式?
✅ 方式一:通过注解配置(推荐)
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用CGLIB
public class AppConfig {
}
✅ 方式二:XML配置
<aop:aspectj-autoproxy proxy-target-class="true"/>
✅ 方式三:@Aspect 切面类中使用
@Aspect
@Component
public class LoggingAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 增强逻辑
}
}
✅ Spring默认行为:
- 有接口 → JDK代理
- 无接口 → CGLIB代理
这种“智能选择”机制极大提升了灵活性。
3. 为什么Spring优先选择JDK代理?
- 更轻量,无额外依赖。
- 更符合“面向接口编程”的设计哲学。
- 在Spring中,大多数Bean(如Service、DAO)都实现了接口。
4. 什么时候必须用CGLIB?
- 你写的类没有接口(如工具类、第三方类)。
- 你想代理
@Controller、@Service等注解类(这些类通常不显式写接口)。 - 你想使用
@EnableAspectJAutoProxy(proxyTargetClass = true)统一使用CGLIB,避免混合代理导致的类型不一致问题(如instanceof判断)。
5. 潜在陷阱:代理对象类型问题
@Service
public class UserService { ... } // 没有接口
@Autowired
private UserService userService; // 正常注入
// 如果用JDK代理,这里会报错!因为JDK代理只能代理接口,而UserService没有接口 → Spring自动用CGLIB
但如果强制用JDK代理(比如配置错误),会抛出:
No qualifying bean of type 'UserService' available: expected at least 1 bean which qualifies as autowire candidate
因为Spring创建的是 Proxy 类型,而你试图注入的是 UserService 类型,类型不匹配。
✅ 所以:没有接口的类,Spring必须用CGLIB,否则无法注入。
五、补充:Spring 5+ 和 Spring Boot 的默认行为
- Spring Boot 2.x+ 默认使用 CGLIB 作为AOP代理机制(从 Spring 5 开始,CGLIB性能已大幅提升,且兼容性更好)。
- 实际上,Spring Boot 默认
proxyTargetClass=false,所以还是优先JDK代理。 - 但绝大多数Spring Boot应用中,
@Service、@Controller都没有接口,所以实际运行中大部分是CGLIB代理。
你可以通过日志查看Spring使用了哪种代理:
logging.level.org.springframework.aop=DEBUG
输出示例:
Creating CGLIB proxy for bean 'userService'
六、总结:如何选择?
| 场景 | 推荐代理方式 |
|---|---|
| 有接口,追求标准、轻量 | ✅ JDK动态代理 |
| 无接口,或代理第三方类 | ✅ CGLIB动态代理 |
| 希望统一代理风格(避免混合) | ✅ 设置 proxyTargetClass=true 强制CGLIB |
| 高性能、高并发系统 | ✅ 两者性能差异极小,可忽略,优先CGLIB简化配置 |
避免代理 final 类或方法 |
❌ 两者都不能代理 final 方法 |
七、扩展:为什么Spring AOP不能代理 private / static / final 方法?
- JDK代理:只代理接口方法 → 无法代理私有方法。
- CGLIB代理:通过继承重写方法 →
private方法不能被子类访问,static方法不能被重写,final方法不能被重写。 - 所以:Spring AOP只能代理 public 方法(无论哪种代理方式)。
💡 提示:如果你的切面没生效,先检查方法是否为
public!
✅ 总结一句话
JDK动态代理基于接口,轻量标准;CGLIB基于继承,强大灵活。Spring AOP根据目标类是否有接口自动选择,优先JDK,无接口则用CGLIB,二者共同支撑了Spring强大的AOP能力。
理解这两种代理机制,是掌握Spring AOP、事务管理、日志切面等核心功能的基石。
浙公网安备 33010602011771号