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 应用的默认选择。 今天就说到这里。下班!!!

相关推荐
我喜欢山,也喜欢海4 分钟前
Jenkins Maven 带权限 搭建方案2025
java·jenkins·maven
明天更新11 分钟前
Java处理压缩文件的两种方式!!!!
java·开发语言·7-zip
铁锚16 分钟前
一个WordPress连续登录失败的问题排查
java·linux·服务器·nginx·tomcat
yychen_java22 分钟前
上云API二开实现三维可视化控制中心
java·无人机
理智的煎蛋23 分钟前
keepalived+lvs
java·开发语言·集成测试·可用性测试
CopyLower37 分钟前
Java与AI技术结合:从机器学习到生成式AI的实践
java·人工智能·机器学习
生命不息战斗不止(王子晗)1 小时前
mybatis中${}和#{}的区别
java·服务器·tomcat
.生产的驴1 小时前
Docker 部署Nexus仓库 搭建Maven私服仓库 公司内部仓库
java·运维·数据库·spring·docker·容器·maven
橙子199110161 小时前
Kotlin 中的 Unit 类型的作用以及 Java 中 Void 的区别
java·开发语言·kotlin
yours_Gabriel1 小时前
【登录认证】JWT令牌
java·开发语言·redis