Spring AOP核心原理:JDK与CGLib动态代理实战解析

Spring AOP 核心原理:JDK 与 CGLIB 动态代理实战解析(2026 最新版)

Spring AOP(Aspect-Oriented Programming)是 Spring 最强大的特性之一,其核心实现完全依赖动态代理。Spring 不会修改你的源代码,而是在运行时生成代理对象,在目标方法调用前后织入增强逻辑(Advice)。

Spring AOP 底层只支持两种动态代理

  • JDK 动态代理(官方推荐接口优先)
  • CGLIB 动态代理(Spring Boot 默认)

下面从原理、源码、实战代码、选择逻辑、性能、陷阱全方位说透。

1. 为什么 Spring AOP 需要动态代理?

AOP 要实现无侵入横切关注点(如事务、日志、安全、缓存),必须在不改动业务代码的前提下拦截方法调用。

代理模式(Proxy Pattern)是最佳方案:

  • 代理对象持有目标对象引用
  • 调用代理 → 代理先执行增强 → 再调用目标方法

Spring 在 Bean 初始化后,通过 AbstractAutoProxyCreator(BeanPostProcessor)为符合切点的 Bean 创建代理。

2. JDK 动态代理详解(Interface-based)

原理

  • JDK 内置 java.lang.reflect.Proxy
  • 只能代理实现了接口的类
  • 运行时生成一个实现目标接口的代理类($Proxy0$Proxy1…)
  • 所有方法调用都通过 InvocationHandler.invoke() 转发

手写 JDK 动态代理完整示例

// 接口
public interface UserService {
    void addUser(String name);
    String getUserName(int id);
}

// 目标类
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String name) {
        System.out.println("添加用户: " + name);
    }

    @Override
    public String getUserName(int id) {
        return "用户" + id;
    }
}

// InvocationHandler(增强逻辑在这里)
public class LogInvocationHandler implements InvocationHandler {
    private final Object target;   // 目标对象

    public LogInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("【JDK代理】方法开始执行: " + method.getName());
        long start = System.currentTimeMillis();

        Object result = method.invoke(target, args);   // 调用目标方法

        long end = System.currentTimeMillis();
        System.out.println("【JDK代理】方法执行结束: " + method.getName() + ", 耗时: " + (end - start) + "ms");
        return result;
    }
}

// 测试
public class JdkProxyTest {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();

        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),      // 类加载器
            target.getClass().getInterfaces(),       // 代理的接口
            new LogInvocationHandler(target)
        );

        proxy.addUser("张三");   // 会触发 invoke
        System.out.println(proxy.getUserName(100));
    }
}

特点

  • 生成的代理类实现目标接口
  • 只能增强接口中声明的方法
  • 性能较高(反射调用,但 JDK 8+ 优化明显)

3. CGLIB 动态代理详解(Class-based)

原理

  • CGLIB(Code Generation Library)通过字节码生成技术(ASM)在运行时生成目标类的子类
  • 代理类是目标类的子类,重写需要增强的方法
  • 通过 MethodInterceptor 拦截方法调用
  • Spring 已将 CGLIB repackage 到 spring-core 中,无需额外依赖

手写 CGLIB 动态代理示例

// 目标类(可以没有接口)
public class OrderService {
    public void createOrder(String orderNo) {
        System.out.println("创建订单: " + orderNo);
    }

    public final void finalMethod() {   // final 方法无法被代理
        System.out.println("这是 final 方法");
    }
}

// MethodInterceptor
public class LogMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, 
                            MethodProxy proxy) throws Throwable {
        System.out.println("【CGLIB代理】方法开始: " + method.getName());

        Object result = proxy.invokeSuper(obj, args);   // 调用父类方法(目标方法)

        System.out.println("【CGLIB代理】方法结束: " + method.getName());
        return result;
    }
}

// 测试
public class CglibProxyTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderService.class);        // 设置父类
        enhancer.setCallback(new LogMethodInterceptor());  // 设置回调

        OrderService proxy = (OrderService) enhancer.create();
        proxy.createOrder("ORD-001");
        proxy.finalMethod();   // final 方法不会被增强
    }
}

特点

  • 可以代理没有实现接口的类
  • 不能代理 final 类和 final 方法(无法继承/重写)
  • 生成的代理类是目标类的子类(OrderService$$EnhancerBySpringCGLIB...

4. Spring AOP 如何选择代理方式(2026 最新机制)

Spring Framework 官方规则AopProxyFactory):

  • 目标 Bean 实现了至少一个接口 → 默认使用 JDK 动态代理
  • 目标 Bean 没有实现接口 → 使用 CGLIB

Spring Boot 默认行为(从 2.0 开始):

  • 默认开启 spring.aop.proxy-target-class=true
  • 统一使用 CGLIB(即使有接口也用 CGLIB)
  • 原因:行为更一致,避免接口注入时的类型转换问题(ClassCastException),对 @Configuration 类等必须 subclassing

配置方式(application.yml 或 @EnableAspectJAutoProxy):

spring:
  aop:
    proxy-target-class: true    # 默认 true(CGLIB)
    # proxy-target-class: false # 强制 JDK(需有接口)
@EnableAspectJAutoProxy(proxyTargetClass = true)  // 强制 CGLIB

Spring 内部代理创建流程

  1. AnnotationAwareAspectJAutoProxyCreator(BeanPostProcessor)
  2. AbstractAutoProxyCreator.createProxy()
  3. DefaultAopProxyFactory 判断创建 JdkDynamicAopProxy 还是 CglibAopProxy
  4. 生成代理对象,替换原始 Bean

5. 实战:在 Spring Boot 中验证代理类型

@Component
public class ProxyTypeChecker implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext ctx) {
        Object bean = ctx.getBean("userServiceImpl");  // 你的 Bean 名

        System.out.println("Bean 类型: " + bean.getClass().getName());
        System.out.println("是否 JDK 代理: " + Proxy.isProxyClass(bean.getClass()));
        System.out.println("是否 CGLIB 代理: " + bean.getClass().getName().contains("$$EnhancerBySpringCGLIB"));
    }
}

查看生成的代理类(调试技巧):

  • 添加 VM 参数:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true(JDK)
  • 或使用 Arthas / JClassLib 查看 $$EnhancerBySpringCGLIB

6. 性能、优缺点与适用场景

维度JDK 动态代理CGLIB 动态代理
代理依据接口类(生成子类)
性能较好(反射优化后)稍差(首次生成字节码有开销)
内存占用较低较高(子类 + FastClass)
限制只能代理接口方法不能代理 final 类/方法
Spring Boot 默认需手动设置 false默认(推荐)
适用场景接口驱动开发、微服务遗留代码、无接口类、@Configuration

结论:现代项目(Spring Boot 3/4)强烈推荐保持默认 CGLIB,除非有明确性能瓶颈或必须用 JDK 的场景。

7. 常见陷阱与最佳实践

  1. 自调用失效(最经典问题)
    this.method() 不会走代理 → 增强逻辑不生效。
    解决:注入自己(@Autowired UserService self)或使用 AopContext.currentProxy()(需开启 expose-proxy=true)。
  2. final 方法/类无法增强 → CGLIB 直接失效。
  3. private 方法无法被 AOP(无论哪种代理)。
  4. 事务嵌套问题 → 常因代理类型导致传播行为异常。

最佳实践

  • 优先定义接口(便于测试 + JDK 代理)
  • 保持 Spring Boot 默认 CGLIB 配置
  • 复杂场景用 ProxyFactory 手动创建代理
  • 性能敏感场景可考虑 AspectJ(编译期织入,非代理)

掌握了 JDK 和 CGLIB 动态代理,就真正理解了 Spring AOP、“@Transactional 为什么能生效”、“为什么 this 调用不走事务”等所有“魔法”的底层。

想继续深入以下任意部分,随时告诉我:

  • Spring AOP 完整源码流程(ProxyFactory → AdvisedSupport)
  • @Aspect 注解如何解析为 Advisor
  • AspectJ vs Spring AOP 对比
  • 自定义 AnnotationAwareAspectJAutoProxyCreator
  • 性能压测对比代码

我可以继续给你更深层次的源码级解析!

文章已创建 4631

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部