代理模式:JDK动态代理与CGLIB字节码增强,Spring AOP的选择策略
代理模式:JDK动态代理与CGLIB字节码增强,Spring AOP的选择策略
适读人群:中高级Java开发者 | 阅读时长:约25分钟 | 模式类型:结构型
开篇故事
2018年刚入职一家互联网公司,接手了一个老项目,里面有几十个 Service 方法,每个方法的开头都是一段完全相同的代码:打印进入日志、记录开始时间、检查权限;结尾又是一段相同的代码:计算耗时、打印退出日志、异常捕获上报。就是这样一大坨完全重复的代码,散落在几十个类里,每次有改动都要改几十个地方。
我当时做的第一件事就是用 Spring AOP 把这些横切逻辑抽出来,几十行的切面代码替代了几千行的重复代码。技术总监看到后说:"这就是代理模式在 Spring 里的正确姿势。"
但使用 Spring AOP 过程中,也踩了不少坑——最典型的就是"同类内部方法调用 AOP 不生效"。直到深入研究了 JDK 动态代理和 CGLIB 的实现原理,才彻底搞清楚为什么会这样,以及如何正确规避。今天把这些都整理出来。
一、模式动机:不修改原始代码来增强功能
代理模式(Proxy Pattern):为另一个对象提供一个替身(代理),以控制对这个对象的访问。
代理的核心是:代理对象与目标对象实现相同的接口(或是目标类的子类),调用者与代理交互,代理在委托给真实对象之前/之后可以执行额外逻辑。
代理的三种主要用途:
- 远程代理:让调用者透明地调用远程对象(RPC 框架的客户端代理)
- 保护代理:控制对原始对象的访问(安全控制、权限检查)
- 虚拟代理/缓存代理:延迟创建开销大的对象,或缓存操作结果(Spring AOP 的
@Cacheable)
二、模式结构
三、JDK 动态代理 vs CGLIB 深度对比
3.1 JDK 动态代理
JDK 动态代理基于接口,只能代理实现了接口的类。
/**
* JDK动态代理原理分析
*/
public class JdkDynamicProxyDemo {
interface OrderService {
Order createOrder(CreateOrderRequest request);
Order getOrder(String orderId);
}
static class OrderServiceImpl implements OrderService {
@Override
public Order createOrder(CreateOrderRequest request) {
System.out.println("Creating order: " + request.getProductId());
return new Order(UUID.randomUUID().toString(), request.getAmount());
}
@Override
public Order getOrder(String orderId) {
System.out.println("Getting order: " + orderId);
return new Order(orderId, BigDecimal.ZERO);
}
}
/**
* 自定义InvocationHandler:方法调用的拦截器
*/
static class MetricsInvocationHandler implements InvocationHandler {
private final Object target; // 被代理的真实对象
MetricsInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
try {
System.out.println("[METRICS] Entering: " + methodName);
Object result = method.invoke(target, args); // 反射调用真实方法
long elapsed = System.currentTimeMillis() - start;
System.out.println("[METRICS] " + methodName + " succeeded in " + elapsed + "ms");
return result;
} catch (InvocationTargetException e) {
long elapsed = System.currentTimeMillis() - start;
System.err.println("[METRICS] " + methodName + " failed in " + elapsed + "ms: " + e.getCause().getMessage());
throw e.getCause(); // 解包真实异常
}
}
}
public static void main(String[] args) {
OrderService realService = new OrderServiceImpl();
// 创建JDK动态代理
OrderService proxy = (OrderService) Proxy.newProxyInstance(
OrderService.class.getClassLoader(), // 类加载器
new Class[]{OrderService.class}, // 要实现的接口
new MetricsInvocationHandler(realService) // InvocationHandler
);
// 代理对象的类名:com.sun.proxy.$Proxy0
System.out.println("Proxy class: " + proxy.getClass().getName());
// 调用代理的方法,会被InvocationHandler拦截
proxy.createOrder(new CreateOrderRequest("PROD-001", new BigDecimal("99.99")));
}
}JDK 动态代理的底层原理:Proxy.newProxyInstance() 在运行时动态生成一个类,这个类实现了指定的接口。生成的代理类大致如下(伪代码):
// JDK自动生成的代理类($Proxy0)结构示意
public final class $Proxy0 extends Proxy implements OrderService {
private static Method m1, m2, m3; // 接口方法的Method对象
static {
m1 = OrderService.class.getMethod("createOrder", CreateOrderRequest.class);
m2 = OrderService.class.getMethod("getOrder", String.class);
}
public $Proxy0(InvocationHandler h) {
super(h); // 把InvocationHandler存到Proxy父类中
}
@Override
public Order createOrder(CreateOrderRequest request) {
// 每个方法都委托给InvocationHandler
return (Order) h.invoke(this, m1, new Object[]{request});
}
@Override
public Order getOrder(String orderId) {
return (Order) h.invoke(this, m2, new Object[]{orderId});
}
}3.2 CGLIB 字节码增强代理
CGLIB 通过继承目标类来创建代理,不需要目标类实现接口:
/**
* CGLIB代理原理演示
*/
public class CglibProxyDemo {
// 注意:没有接口!CGLIB可以直接代理普通类
static class InventoryService {
public int checkStock(String skuId) {
System.out.println("Checking stock for: " + skuId);
return 100; // 库存数量
}
public void reserveStock(String skuId, int quantity) {
System.out.println("Reserving " + quantity + " units of " + skuId);
}
// final方法无法被CGLIB代理(无法被子类覆写)
public final String getServiceName() {
return "InventoryService";
}
}
/**
* CGLIB的拦截器(类似JDK的InvocationHandler)
*/
static class LoggingMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("[LOG] Before: " + method.getName());
// 注意:这里用proxy.invokeSuper()而不是method.invoke()
// invokeSuper调用的是父类(目标类)的方法,不会再次触发拦截,避免死循环
// 而method.invoke(obj, args)会触发拦截,导致无限递归
Object result = proxy.invokeSuper(obj, args);
System.out.println("[LOG] After: " + method.getName());
return result;
}
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(InventoryService.class); // 设置父类(被代理类)
enhancer.setCallback(new LoggingMethodInterceptor()); // 设置拦截器
InventoryService proxy = (InventoryService) enhancer.create();
// 代理类是InventoryService的子类
System.out.println("Proxy class: " + proxy.getClass().getSuperclass().getName());
System.out.println("Is InventoryService: " + (proxy instanceof InventoryService));
proxy.checkStock("SKU-001"); // 会被拦截
proxy.reserveStock("SKU-001", 5); // 会被拦截
proxy.getServiceName(); // final方法,不会被拦截!
}
}3.3 Spring AOP 的代理选择策略
Spring AOP 在以下情况使用 JDK 动态代理:
- 目标类实现了至少一个接口
Spring AOP 在以下情况使用 CGLIB:
- 目标类没有实现任何接口
- 配置了
@EnableAspectJAutoProxy(proxyTargetClass = true) - Spring Boot 2.0+ 默认使用 CGLIB(
spring.aop.proxy-target-class=true)
// Spring AOP代理创建的核心逻辑(DefaultAopProxyFactory)
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 判断是否使用CGLIB
if (!config.isOptimize() && !config.isProxyTargetClass() &&
!hasNoUserSuppliedProxyInterfaces(config)) {
// 使用JDK动态代理
return new JdkDynamicAopProxy(config);
} else {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("Target source cannot determine target class");
}
// 如果目标类是接口或已经是代理类,还是用JDK代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 否则用CGLIB
return new ObjenesisCglibAopProxy(config);
}
}
}四、生产级代码实现
4.1 完整的 AOP 切面实现
/**
* 统一的操作日志切面
* 记录所有Controller层的接口调用,包含:请求参数、响应结果、耗时、用户信息
*/
@Aspect
@Component
@Slf4j
public class OperationLogAspect {
@Autowired
private OperationLogRepository logRepository;
@Autowired
private UserContextHolder userContextHolder;
/**
* 切入点:所有@RestController标注的类中的public方法
*/
@Pointcut("within(@org.springframework.web.bind.annotation.RestController *) && " +
"execution(public * *(..))")
public void controllerMethods() {}
/**
* 环绕通知:记录请求和响应
*/
@Around("controllerMethods()")
public Object logOperation(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 获取方法签名信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = method.getName();
// 获取当前用户信息
UserContext userContext = userContextHolder.getCurrentUser();
// 构建请求日志
OperationLog operationLog = OperationLog.builder()
.traceId(MDC.get("traceId"))
.operatorId(userContext != null ? userContext.getUserId() : null)
.operatorName(userContext != null ? userContext.getUsername() : "anonymous")
.className(className)
.methodName(methodName)
.requestParams(serializeArgs(joinPoint.getArgs()))
.requestTime(LocalDateTime.now())
.build();
Object result = null;
try {
result = joinPoint.proceed(); // 执行目标方法
long elapsed = System.currentTimeMillis() - startTime;
operationLog.setSuccess(true);
operationLog.setResponseData(serializeResult(result));
operationLog.setExecutionTimeMs(elapsed);
log.info("[AUDIT] {}.{} success, elapsed: {}ms", className, methodName, elapsed);
return result;
} catch (Throwable e) {
long elapsed = System.currentTimeMillis() - startTime;
operationLog.setSuccess(false);
operationLog.setErrorMessage(e.getMessage());
operationLog.setExecutionTimeMs(elapsed);
log.error("[AUDIT] {}.{} failed, elapsed: {}ms, error: {}",
className, methodName, elapsed, e.getMessage());
throw e;
} finally {
// 异步保存日志,不阻塞业务响应
operationLog.setResponseTime(LocalDateTime.now());
saveLogAsync(operationLog);
}
}
/**
* 安全地序列化方法参数(过滤敏感字段)
*/
private String serializeArgs(Object[] args) {
if (args == null || args.length == 0) return "[]";
try {
List<Object> sanitizedArgs = Arrays.stream(args)
.map(this::sanitize)
.collect(Collectors.toList());
return JsonUtils.toJson(sanitizedArgs);
} catch (Exception e) {
return "[serialization failed]";
}
}
private Object sanitize(Object arg) {
if (arg instanceof String s && (s.contains("password") || s.contains("secret"))) {
return "***REDACTED***";
}
// 可以用反射检查字段注解来过滤标记为@Sensitive的字段
return arg;
}
private String serializeResult(Object result) {
if (result == null) return "null";
try {
// 限制响应体日志大小,避免大响应体把日志撑爆
String json = JsonUtils.toJson(result);
return json.length() > 1000 ? json.substring(0, 1000) + "...[truncated]" : json;
} catch (Exception e) {
return "[serialization failed]";
}
}
@Async("auditLogExecutor")
public void saveLogAsync(OperationLog log) {
try {
logRepository.save(log);
} catch (Exception e) {
// 日志保存失败不应影响业务,只做告警
log.error("Failed to save operation log", e);
}
}
}
/**
* 分布式锁切面
*/
@Aspect
@Component
@Slf4j
public class DistributedLockAspect {
@Autowired
private RedissonClient redissonClient;
@Around("@annotation(distributedLock)")
public Object aroundWithLock(ProceedingJoinPoint joinPoint,
DistributedLock distributedLock) throws Throwable {
// 从注解获取锁的key(支持SpEL表达式)
String lockKey = resolveLockKey(distributedLock.key(), joinPoint);
long waitTime = distributedLock.waitTime();
long leaseTime = distributedLock.leaseTime();
RLock lock = redissonClient.getLock("lock:" + lockKey);
boolean acquired = false;
try {
acquired = lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);
if (!acquired) {
throw new LockAcquisitionException(
"Failed to acquire lock for key: " + lockKey + " within " + waitTime + "ms");
}
return joinPoint.proceed();
} finally {
if (acquired && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
private String resolveLockKey(String keyExpression, ProceedingJoinPoint joinPoint) {
// 使用SpEL解析锁key,支持 "#orderId" 这样的参数引用
if (!keyExpression.contains("#")) {
return keyExpression;
}
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String[] paramNames = signature.getParameterNames();
Object[] args = joinPoint.getArgs();
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < paramNames.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
ExpressionParser parser = new SpelExpressionParser();
return parser.parseExpression(keyExpression).getValue(context, String.class);
}
}4.2 解决 Spring AOP 同类内部调用失效问题
/**
* 问题场景:同类内部调用,AOP不生效
*/
@Service
public class OrderService {
@Transactional // 期望这个事务注解通过AOP生效
public Order createOrder(CreateOrderRequest request) {
Order order = buildOrder(request);
// ... 保存order
// 问题:这里调用的是 this.notifyUser(),不是代理对象的notifyUser()
// AOP代理是在外部调用时生效的,this调用绕过了代理!
this.notifyUser(order);
return order;
}
@Async // 期望异步执行,但实际是同步的
public void notifyUser(Order order) {
// 发送通知...
}
}
/**
* 解决方案一:通过Spring容器获取自身代理
* (这是最常见的解决方案,但侵入性强)
*/
@Service
public class OrderService implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Transactional
public Order createOrder(CreateOrderRequest request) {
Order order = buildOrder(request);
// 获取自身的代理对象,通过代理调用才会触发AOP
OrderService selfProxy = applicationContext.getBean(OrderService.class);
selfProxy.notifyUser(order); // 通过代理调用,@Async会生效
return order;
}
@Async
public void notifyUser(Order order) {
// 真正异步执行
}
}
/**
* 解决方案二:拆分到两个Bean
* (更好的方案,符合单一职责)
*/
@Service
public class OrderService {
@Autowired
private OrderNotificationService notificationService; // 分离到独立的Bean
@Transactional
public Order createOrder(CreateOrderRequest request) {
Order order = buildOrder(request);
notificationService.notifyUser(order); // 跨Bean调用,AOP正常生效
return order;
}
}@Service
public class OrderNotificationService {
@Async
public void notifyUser(Order order) {
// 异步执行
}
}五、踩坑实录
坑一:CGLIB 代理要求被代理类有无参构造函数
CGLIB 通过继承创建子类代理,子类的构造函数需要调用父类构造函数。如果目标类只有有参构造函数,CGLIB 会尝试调用无参构造函数,找不到就会报错。
Spring Boot 2.0+ 使用 Objenesis 库可以绕过这个限制(不调用构造函数直接创建对象),但如果你在非 Spring 环境下直接使用 CGLIB,就必须确保被代理类有无参构造函数。
坑二:@Transactional 在 private 方法上不生效
无论是 JDK 代理还是 CGLIB 代理,private 方法都无法被代理拦截。CGLIB 创建子类,子类无法覆盖父类的 private 方法;JDK 代理只能拦截接口中定义的方法,private 方法不在接口中。
所以 @Transactional 加在 private 方法上完全没有效果,编译器不会报错,运行时也不会报错,但事务就是不生效,这个坑很隐蔽。
坑三:@Transactional 与 @Async 一起使用的陷阱
如果一个方法同时有 @Transactional 和 @Async,在 @Async 执行之前事务就已经提交了(因为 @Async 方法在新线程里执行,新线程不继承父线程的事务)。所以在异步方法中无法回滚同步方法中的事务,这两个注解加在同一个方法上通常没有意义。
正确的做法是:同步方法负责事务,异步方法只做不需要事务的后续操作(如发邮件、发通知)。
六、总结
JDK 动态代理和 CGLIB 是 Spring AOP 的两大底层实现。理解它们的区别,对于排查 AOP 失效问题至关重要:
| 维度 | JDK 动态代理 | CGLIB |
|---|---|---|
| 代理方式 | 实现接口 | 继承目标类 |
| 要求 | 目标类必须实现接口 | 目标类不能是 final |
| 性能 | 调用时稍慢(反射) | 调用时稍快(字节码) |
| Spring Boot 2.x 默认 | 否 | 是 |
private/final 方法 | 不能代理 | 不能代理 |
| 同类内部调用 | 不生效 | 不生效 |
代理模式的核心约束:调用必须通过代理对象,绕过代理的调用(this.xxx()、private方法、final方法)都不会被拦截。这是 Spring AOP 绝大多数"不生效"问题的根本原因。
