Spring AOP源码-JDK 与 CGLIB 动态代理的抉择与实现

说到AOP,重点就会说动态代理,动态代理是AOP的基石,核心是在运行时生成代理对象,拦截目标方法并嵌入横切的逻辑,Spring是支持2种动态代理,JDK动态代理CGLIB代理,二者的选择不仅影响性能而且决定了某些功能的可用性。本节我们就重点说下二者的区别。

1. JDK动态代理的源码实现

JDK动态代理的核心在 JdkDynamicAopProxy里,这一段的源码太多了:

kotlin 复制代码
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable 

整个类是继承了AopProxy、InvocationHandler 和 Serializable。这里我们这个类里的主要流程来看一下:

typescript 复制代码
// JdkDynamicAopProxy.java
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
    // 代理配置信息(目标对象、Advisor 链等)
    private final AdvisedSupport advised;

    // 生成代理对象,这里比较重要的是Proxy.newProxyInstance
    public Object getProxy() {
        return Proxy.newProxyInstance(
            getClass().getClassLoader(), 
            this.advised.getProxiedInterfaces(), 
            this // InvocationHandler 自身
        );
    }

    // 方法拦截逻辑
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 获取目标对象
        TargetSource targetSource = this.advised.targetSource;
        Object target = targetSource.getTarget();

        // 2. 获取匹配的拦截器链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, target.getClass());

        // 3. 链式执行拦截器
        if (chain.isEmpty()) {
            return method.invoke(target, args);
        } else {
            MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, target.getClass(), chain);
            return invocation.proceed();
        }
    }
}

重点解析:首先整个过程:

第一步是配置一些信息,比如日志、cache一类的。

第二步就是要生成代理对象,通过return Proxy.newProxyInstance,这里我们要进入这个方法看一下,代理对象是如何生成的

scss 复制代码
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
        throws IllegalArgumentException {
    Objects.requireNonNull(h);

    // 克隆接口数组,防止外部修改
    final Class<?>[] intfs = interfaces.clone();
    
    // 获取代理类(可能从缓存中获取或生成)
    Class<?> cl = getProxyClass0(loader, intfs);

    // 通过反射获取代理类的构造方法并创建实例
    try {
        Constructor<?> cons = cl.getConstructor(InvocationHandler.class);
        return cons.newInstance(h);
    } catch (Exception e) {
        throw new InternalError(e.toString(), e);
    }
}

上面代码的解释我都写在上面,需要注意的是

参数:

  • loader:类加载器,用于加载动态生成的代理类
  • interfaces:代理类需要实现的接口数组
  • h:InvocationHandler实例,负责处理方法调用

而整个的逻辑是:

  • 调用 getProxyClass0 获取代理类的 Class 对象。
  • 使用反射获取代理类的构造方法(接收 InvocationHandler 参数)。
  • 创建代理类实例并返回。

这里getProxyClass0负责获取或生成代理类的Class对象,如果缓存中不存在的话,那就通过ProxyClassFactory 生成代理类的字节码。而ProxyClassFactory 是实际生成代理类字节码的工厂类,位于Proxy类内部。

第三步就是整个方法拦截的逻辑步骤了,这里就是invoke方法具体实现

typescript 复制代码
public interface InvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

在这里,所有代理的方法调用均路由到 invoke 方法里,根据方法签名匹配所有适合的Advisor,生成拦截器链。然后递归执行拦截器链和目标方法。这里就把JDK动态代理的源码说清楚了,最后再说一下这里的源码中的关键优化有什么:

  • 缓存机制:缓存代理类,避免重复生成字节码
  • 弱引用:允许GC回收未使用的代理类
  • 反射优化:通过Method对象和反射调用,减少性能开销

2. CGLIB动态代理的源码实现

CGLIB动态代理的核心是通过字节码生成技术在运行时动态创建目标类的子类,子类重写目标类的非 final 方法,并在方法调用时插入拦截逻辑。CGLIB的核心源码大概有三个核心关键类,我们一个一个来说:

2.1 Enhancer类

这个类是CGLIB的入口类,负责配置和生成代理类 ,核心方法为create:

scss 复制代码
public Object create() {
    classOnly = false;
    argumentTypes = null;
    return createHelper();
}

private Object createHelper() {
    // 验证配置
    validate();
    if (superclass != null) {
        setNamePrefix(superclass.getName());
    } else if (interfaces != null) {
        setNamePrefix(interfaces[ReflectUtils.findPackageProtected(interfaces)].getName());
    }
    // 生成代理类
    return super.create(KEY_FACTORY.newInstance(
        superclass != null ? superclass.getName() : null,
        ReflectUtils.getNames(interfaces),
        filter,
        callbackTypes,
        useFactory,
        interceptDuringConstruction,
        serialVersionUID
    ));
}

通过方法设置拦截器,调用父类的create方法生成代理类的字节码,然后KEY_FATORY生成缓存键,避免重复生成相同的代理类。

2.2 AbstractClassGenerator

这是Enhancer的父类,里面的方法generateClass负责实际的字节码生成,并通过类加载器加载字节码,生成Class对象

typescript 复制代码
protected Class generate(LocalClassLoaderData data) {
    // 生成字节码
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    try {
        generateClass(cw);
        return loadClass(data, cw.toByteArray());
    } catch (RuntimeException e) {
        throw e;
    } catch (Error e) {
        throw e;
    } catch (Exception e) {
        throw new CodeGenerationException(e);
    }
}

2.3 MethodInterceptor接口

这个实现拦截逻辑的核心接口,定义如下:

typescript 复制代码
public interface MethodInterceptor extends Callback {
    Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}

而整个CGLIB的优化也是在这里,MethodProxy是CGLIB的优化机制,用于高效调用目标类或者代理类的方法,MethodProxy实际上封装了目标类和代理类的方法签名,使用FirstClass机制直接调用方法,绕过反射。那如何实现的呢?我们来看这个例子。

typescript 复制代码
public class Target$$FastClassByCGLIB$$1234 {
    public Object invoke(int index, Object obj, Object[] args) {
        if (index == 0) {
            ((Target) obj).someMethod((String) args[0]);
        }
        // 其他方法
        return null;
    }
}

具体是这样的,FirstClass为目标类和代理类各生成一个类,维护方法索引。那方法调用的时候通过索引直接分发,性能接近直接调用。

这样整个的过程是这样的:

配置Enhancer ->字节码生成 ->类加载 ->实例化 ->方法调用

3. 代理选择策略与性能对比

Spring通过DefaultAopProxyFactory决定使用哪种代理

arduino 复制代码
// DefaultAopProxyFactory.java
public AopProxy createAopProxy(AdvisedSupport config) {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config); // 使用 CGLIB
    } else {
        return new JdkDynamicAopProxy(config); // 使用 JDK 代理
    }
}

整个决策条件:

  • proxyTargetClass=true:强制使用 CGLIB。
  • 目标类无接口:自动选择 CGLIB。
  • 显式指定优化(optimize=true):优先 CGLIB。

两者对比:

  • 实现机制: JDK 动态代理:通过实现接口生成代理类,基于反射调用 InvocationHandler.invoke。 CGLIB:通过继承目标类生成子类,基于字节码操作和 MethodInterceptor。
  • 适用场景: JDK 动态代理:只能代理实现接口的类,适合接口驱动设计。 CGLIB:可以代理普通类,适合无接口场景(如 Spring AOP 中的类代理)。
  • 性能: CGLIB:通过 FastClass 和 MethodProxy 优化,调用效率高于 JDK 动态代理的反射机制,尤其在高频调用场景。 JDK 动态代理:反射调用有一定开销,但实现简单,无需额外依赖。
  • 局限性: CGLIB:不能代理 final 类或 final 方法,因为无法继承或重写。 JDK 动态代理:必须实现接口,灵活性较低。

4. 总结

JDK 代理基于接口,CGLIB 代理基于继承,二者各有适用场景。Spring 根据目标类特性自动选择代理方式,也可通过配置强制指定。CGLIB 的性能优势使其成为现代 Spring 应用的默认选择。 今天就说到这里。下班!!!

相关推荐
四谎真好看22 分钟前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
桦说编程28 分钟前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t28 分钟前
ZIP工具类
java·zip
lang201509281 小时前
Spring Boot优雅关闭全解析
java·spring boot·后端
pengzhuofan2 小时前
第10章 Maven
java·maven
百锦再2 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
刘一说2 小时前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端
壹佰大多3 小时前
【spring如何扫描一个路径下被注解修饰的类】
java·后端·spring
百锦再3 小时前
对前后端分离与前后端不分离(通常指服务端渲染)的架构进行全方位的对比分析
java·开发语言·python·架构·eclipse·php·maven
DokiDoki之父3 小时前
Spring—注解开发
java·后端·spring