Skip to content

Spring AOP 原理:JDK 动态代理 vs CGLIB

提出问题

AOP 是 Spring 最核心的能力之一,但很多开发者只停留在「@Aspect 加 @Around 就能切面」的会用层面,问到底层实现就卡住了。面试官问这道题,核心考察三点:两种代理方式的实现原理Spring 什么时候自动切换、以及自调用为什么会失效。这三个问题从源码级理解,才算真正搞懂了 Spring AOP。

分析问题

两种代理方式

Spring AOP 基于代理模式实现,底层有两种代理方式:JDK 动态代理和 CGLIB。

JDK 动态代理:基于 java.lang.reflect.ProxyInvocationHandler,运行时生成一个实现了目标类所有接口的代理类。代理类与目标类实现相同的接口,但不继承目标类。因此,JDK 代理只能代理接口中声明的方法,不能代理目标类自己定义的、不在接口中的方法。

java
// 目标类实现了接口
public interface UserService {
    void createUser(String name);
}
public class UserServiceImpl implements UserService {
    @Override
    public void createUser(String name) {
        System.out.println("创建用户: " + name);
    }
    // 非接口方法,JDK 代理无法拦截
    public void internalMethod() {
        System.out.println("内部方法");
    }
}

// JDK 动态代理实现
public class JdkProxyFactory {
    public static UserService createProxy(UserService target) {
        return (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            (proxy, method, args) -> {
                System.out.println("[AOP前置] 方法: " + method.getName());
                Object result = method.invoke(target, args);
                System.out.println("[AOP后置] 方法: " + method.getName());
                return result;
            }
        );
    }
}

// 使用
UserService proxy = JdkProxyFactory.createProxy(new UserServiceImpl());
proxy.createUser("张三");   // AOP生效
// proxy.internalMethod(); // 编译都不通过,因为 UserService 接口没这个方法

CGLIB 代理:基于 ASM 字节码技术,运行时生成目标类的子类,通过方法拦截(MethodInterceptor)实现增强。因为生成的是子类,所以可以代理所有非 final 的方法,包括目标类自己定义的、不在接口中的方法。但 final 方法和 final 类无法被代理(子类无法重写 final 方法,无法继承 final 类)。

java
// 目标类没有实现接口
public class ReportService {
    public void generateReport() {
        System.out.println("生成报告");
    }
    // final 方法,CGLIB 不能代理
    public final void logSteps() {
        System.out.println("记录日志");
    }
}

// CGLIB 代理实现(需要 cglib 依赖)
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;

public class CglibProxyFactory {
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(Class<T> targetClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);
        enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
            System.out.println("[AOP前置] 方法: " + method.getName());
            Object result = proxy.invokeSuper(obj, args);
            System.out.println("[AOP后置] 方法: " + method.getName());
            return result;
        });
        return (T) enhancer.create();
    }
}

// 使用
ReportService proxy = CglibProxyFactory.createProxy(ReportService.class);
proxy.generateReport(); // AOP生效
proxy.logSteps();       // 执行了,但AOP不生效(final方法无法拦截)

Spring 自动切换策略

Spring 通过 DefaultAopProxyFactory 决定使用哪种代理方式,核心逻辑极其简单:

java
// DefaultAopProxyFactory.createAopProxy()
public AopProxy createAopProxy(AdvisedSupport config) {
    // 条件1: 配置了 optimize=true 或 proxyTargetClass=true 或 没有接口
    if (config.isOptimize() || config.isProxyTargetClass() ||
        hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        // 条件2: 目标类是接口 或 是 JDK 代理类,走 JDK 代理
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    } else {
        // 有接口且没设置 proxyTargetClass,走 JDK 代理
        return new JdkDynamicAopProxy(config);
    }
}

从 Spring Boot 2.x 开始,默认策略改为 优先使用 CGLIB,即使目标类实现了接口。这是因为 spring.aop.proxy-target-class=true 在 Spring Boot 中默认开启。可以通过 @EnableAspectJAutoProxy(proxyTargetClass = false) 强制切换回 JDK 代理。

自调用问题

这是 AOP 面试中最经典也最容易答不完整的坑。自调用指同一个类中,方法 A 调用方法 B,如果 B 有 @Transactional 或 @Async 等注解,增强不会生效

java
@Service
public class OrderService {
    @Transactional
    public void createOrder(Order order) {
        saveOrder(order);
        // 调用本类的 sendNotification——这里走的是 this,不是代理对象
        this.sendNotification(order);
    }

    @Async
    public void sendNotification(Order order) {
        // 异步不会生效!因为 this 是原始对象,不是代理对象
        emailService.send(order);
    }
}

原因:Spring 容器中的是代理对象,但 this.sendNotification() 调用的是目标对象(原始 bean)的方法,根本没有经过代理调用链。AOP 增强失效。

解法有两种

java
// 解法1:注入自身代理(注意循环依赖风险)
@Service
public class OrderService {
    @Autowired
    private OrderService self; // 注入的是代理对象

    @Transactional
    public void createOrder(Order order) {
        saveOrder(order);
        self.sendNotification(order); // 通过代理对象调用
    }

    @Async
    public void sendNotification(Order order) {
        emailService.send(order);
    }
}

// 解法2:AopContext.currentProxy()(需要 exposeProxy=true)
@Service
@EnableAspectJAutoProxy(exposeProxy = true)
public class OrderService {
    @Transactional
    public void createOrder(Order order) {
        saveOrder(order);
        ((OrderService) AopContext.currentProxy()).sendNotification(order);
    }
}

Spring 6 的变化

Spring 6 中 CGLIB 被替换为 ByteBuddyObjenesisCglibAopProxy 改为 ObjenesisByteBuddyAopProxy),Spring Boot 3 不再直接依赖 CGLIB。但对外 API 完全兼容,开发者不需要改代码。ByteBuddy 的优势在于:更现代的字节码操作 API、更好的性能、更少的依赖冲突。

总结

对比维度JDK 动态代理CGLIB / ByteBuddy
原理生成接口实现类生成目标类子类
依赖要求必须实现至少一个接口不需要接口
可代理范围仅接口方法非 final 方法
性能创建快,调用慢创建慢,调用快
Spring Boot 默认否(除非显式关闭 proxyTargetClass)
自调用失效失效(同)

生产避坑:自调用是 AOP 失效的第一大原因,排查时先看调用链走了 this 还是代理对象。另一个常见坑是 @Async@Transactional 混用——两者都依赖 AOP 代理,如果同时在同一个类中,@Transactional 的代理在 @Async 的代理之内,注意两层代理的执行顺序。Spring 6 之后 ByteBuddy 替代 CGLIB,但两者行为完全一致,不需要迁移存量代码。

参考:Spring 源码 — DefaultAopProxyFactory、JdkDynamicAopProxy、CglibAopProxy;Spring Boot 自动配置 — AopAutoConfiguration;Spring 6 迁移指南 — CGLIB replaced by ByteBuddy

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。