SpringAOP两种代理机制
Spring AOP 代理机制详解:CGLIB vs JDK Proxy
前言
在使用 Spring AOP 进行调试时,你是否遇到过这样的困惑:明明断点打在了业务方法上,Step Over 后却跳进了一堆 CGLIB$$ 开头的类?这背后就是 Spring AOP 的代理机制在起作用。本文将深入介绍 Spring AOP 的两种代理方式:JDK 动态代理 和 CGLIB 代理。
什么是 AOP 代理?
Spring AOP 的核心思想是在不修改原始代码的情况下,为方法调用织入额外的逻辑(如日志、事务、权限校验等)。为了实现这一点,Spring 需要为目标对象创建一个代理对象,由代理对象拦截方法调用,执行切面逻辑后再调用原始方法。
Spring 提供了两种创建代理的方式:
| 特性 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 实现原理 | 基于 java.lang.reflect.Proxy,生成接口的实现类 |
基于字节码生成库,生成目标类的子类 |
| 前提条件 | 目标类必须实现接口 | 目标类不能是 final |
| 代理对象类型 | 接口类型 | 目标类的子类类型 |
| 注入方式 | 只能用接口类型声明注入 | 接口类型和实现类类型都可以 |
| 性能 | 方法调用通过反射,略慢 | 方法调用通过 FastClass 机制,略快 |
| Spring Boot 默认 | ❌ 非默认(1.x 时代的默认) | ✅ 默认(2.x 起) |
JDK 动态代理
原理
JDK 动态代理要求目标类必须实现至少一个接口。Spring 会使用 java.lang.reflect.Proxy 生成一个实现了相同接口的代理类,在代理类中拦截方法调用。
示例
定义接口:
public interface OrderService {
String createOrder(String productId, int quantity);
}
实现类:
@Service
public class OrderServiceImpl implements OrderService {
@Override
public String createOrder(String productId, int quantity) {
String orderId = UUID.randomUUID().toString();
System.out.println("创建订单: " + orderId);
return orderId;
}
}
切面:
@Aspect
@Component
public class LogAspect {
@Around("execution(* com.example.service.impl.*.*(..))")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().getName();
System.out.println("[LOG] 方法开始: " + methodName);
Object result = pjp.proceed();
System.out.println("[LOG] 方法结束: " + methodName + ", 返回: " + result);
return result;
}
}
启用 JDK Proxy:
# application.properties
spring.aop.proxy-target-class=false
注入时必须使用接口类型:
@RestController
public class OrderController {
// ✅ 正确:使用接口类型
@Autowired
private OrderService orderService;
// ❌ 错误:使用实现类类型会报 BeanNotOfRequiredTypeException
// @Autowired
// private OrderServiceImpl orderService;
}
JDK Proxy 的调用链路
客户端调用 orderService.createOrder()
→ JDK Proxy ($Proxy42)
→ InvocationHandler.invoke()
→ LogAspect.logAround()
→ OrderServiceImpl.createOrder() // 实际业务方法
手动实现 JDK 动态代理(帮助理解原理)
public class JdkProxyDemo {
public static void main(String[] args) {
OrderService target = new OrderServiceImpl();
OrderService proxy = (OrderService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{OrderService.class},
(proxyObj, method, methodArgs) -> {
System.out.println("[Proxy] 前置处理: " + method.getName());
Object result = method.invoke(target, methodArgs);
System.out.println("[Proxy] 后置处理: " + method.getName());
return result;
}
);
proxy.createOrder("P001", 2);
}
}
输出:
[Proxy] 前置处理: createOrder
创建订单: xxxx-xxxx-xxxx
[Proxy] 后置处理: createOrder
CGLIB 代理
原理
CGLIB(Code Generation Library)通过字节码生成技术,在运行时动态生成目标类的子类作为代理。它不要求目标类实现接口,但要求目标类和目标方法不能是 final 的(因为 final 无法被继承/重写)。
示例
不实现接口的类也可以被代理:
@Service
public class NotificationService {
public void sendEmail(String recipient, String content) {
System.out.println("发送邮件给: " + recipient);
}
public void sendSms(String phone, String content) {
System.out.println("发送短信给: " + phone);
}
}
切面同样生效:
@Aspect
@Component
public class LogAspect {
@Around("execution(* com.example.service.NotificationService.*(..))")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("[LOG] 方法开始: " + pjp.getSignature().getName());
Object result = pjp.proceed();
System.out.println("[LOG] 方法结束: " + pjp.getSignature().getName());
return result;
}
}
Spring Boot 2.x+ 默认就是 CGLIB,无需额外配置。
注入时接口类型和实现类类型都可以:
@RestController
public class NotificationController {
// ✅ 都可以正常工作
@Autowired
private NotificationService notificationService;
@Autowired
private NotificationServiceImpl notificationService;
}
CGLIB 的调用链路
客户端调用 notificationService.sendEmail()
→ CGLIB 代理 (NotificationService$$EnhancerBySpringCGLIB$$a1b2c3d4)
→ MethodInterceptor.intercept()
→ LogAspect.logAround()
→ NotificationService.sendEmail() // 实际业务方法
手动实现 CGLIB 代理(帮助理解原理)
public class CglibProxyDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(NotificationService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, methodArgs, proxy) -> {
System.out.println("[CGLIB] 前置处理: " + method.getName());
Object result = proxy.invokeSuper(obj, methodArgs);
System.out.println("[CGLIB] 后置处理: " + method.getName());
return result;
});
NotificationService proxy = (NotificationService) enhancer.create();
proxy.sendEmail("user@example.com", "Hello");
}
}
输出:
[CGLIB] 前置处理: sendEmail
发送邮件给: user@example.com
[CGLIB] 后置处理: sendEmail
Spring Boot 为什么默认选择 CGLIB?
在 Spring Boot 1.x 时代,默认使用 JDK 动态代理。但从 Spring Boot 2.0 开始,默认改为了 CGLIB。这个决策的核心原因是:
JDK Proxy 的"类型注入陷阱":
// 假设 UserService 接口有实现类 UserServiceImpl
// ✅ JDK Proxy 下正确的写法
@Autowired
private UserService userService;
// ❌ JDK Proxy 下会报错!
// 因为代理对象是 $Proxy 类型,不是 UserServiceImpl 类型
@Autowired
private UserServiceImpl userService;
// 报错: BeanNotOfRequiredTypeException:
// Bean named 'userServiceImpl' is expected to be of type 'UserServiceImpl'
// but was actually of type 'com.sun.proxy.$Proxy42'
这个问题对新手来说非常困惑,而 CGLIB 代理生成的是目标类的子类,天然兼容两种注入方式,所以 Spring Boot 团队选择了 CGLIB 作为默认方案。
调试技巧:如何跳过代理类
使用 CGLIB 代理后,调试时 Step Over/Into 会进入大量代理类代码。以下是解决方案:
方案一:断点打在方法体内部
public String createOrder(String productId, int quantity) {
String orderId = UUID.randomUUID().toString(); // ← 断点打这里,而不是方法签名
return orderId;
}
方案二:IntelliJ IDEA 配置 Step Filter
Settings → Build, Execution, Deployment → Debugger → Stepping
勾选 "Do not step into the classes",添加:
net.sf.cglib.*
org.springframework.cglib.*
com.sun.proxy.*
org.springframework.aop.*
同时勾选 "Skip synthetic methods"。
配置后,调试时会自动跳过代理类,直接到达业务代码。
常见问题 FAQ
Q1: 实现了接口的类,Spring Boot 还是用 CGLIB 吗?
是的。 Spring Boot 2.x+ 默认 proxy-target-class=true,无论是否实现接口,都使用 CGLIB。除非你手动设置 spring.aop.proxy-target-class=false。
Q2: CGLIB 有什么限制?
- 目标类不能是
final(无法生成子类) - 目标方法不能是
final或private(无法被重写) - 构造函数会被调用两次(目标对象一次,代理对象一次),Spring 4.0+ 通过 Objenesis 优化了这个问题
Q3: 两者性能差异大吗?
在现代 JVM 和 Spring 版本下,差异可以忽略不计。CGLIB 使用 FastClass 机制避免了反射调用,在高频调用场景下甚至略优于 JDK Proxy。
Q4: 什么时候应该用 JDK Proxy?
一般不需要刻意选择。但如果你的项目有以下需求,可以考虑 JDK Proxy:
- 严格遵循面向接口编程,希望强制所有注入都使用接口类型
- 需要减少生成的字节码类数量(JDK Proxy 不生成额外的 class 文件)
总结
| 维度 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 实现方式 | 接口代理 | 子类代理 |
| 是否需要接口 | ✅ 必须 | ❌ 不需要 |
| final 类/方法 | 可以代理(只要接口方法不是 final) | ❌ 不能代理 |
| 注入灵活性 | 只能用接口类型 | 接口和实现类都可以 |
| Spring Boot 默认 | 1.x 默认 | 2.x+ 默认 |
| 调试体验 | 较好(代理类较简单) | 需配置 Step Filter |
实际开发建议:保持 Spring Boot 默认的 CGLIB 代理即可,配合 IDE 的 Step Filter 功能解决调试体验问题。专注于业务代码的编写,代理机制交给框架处理。

浙公网安备 33010602011771号