Skip to content
Go back

Spring AOP——JDK动态代理和CGLIB的核心区别

Spring AOP:JDK 动态代理 vs CGLIB 的底层对决

一句话结论(30s)

JDK 动态代理与 CGLIB 的本质区别在于「基于接口 + 反射」vs「基于子类继承 + 索引直调」。CGLIB 更快,是因为它用 FastClass 机制按方法 index 直接调用,绕开了 Method.invoke() 的双层反射开销,比 JDK 代理快 2-3 倍。它的代价是不能代理 final 类/方法,因为子类继承要求方法可重写;而 Spring Boot 2.x 默认强制 CGLIB,是因为字段注入 @Autowired UserServiceImpl 需要代理对象是 UserServiceImpl 实例,JDK 代理只实现接口、不是实现类。

思考:JDK 代理和 CGLIB 各自适用什么场景?

判断标准是「有没有接口」和「要不要代理实现类」。类实现了一个清晰的接口时,JDK 动态代理轻量、稳定、JDK 内置零依赖,是更朴素的选择;而遇到「没有接口」「按实现类注入(@Autowired UserServiceImpl)」或「需要代理具体类的方法」时,JDK 就无能为力了,只能上 CGLIB。反过来,如果目标是 final 类、final 方法或 private 方法,CGLIB 的「子类继承」机制也代理不了——这时只能靠改造代码(抽接口、去掉 final)。

核心原理(2min)

JDK 动态代理生成的 $Proxy0 继承 java.lang.reflect.Proxy 并实现目标接口,每次调用都经过 InvocationHandler.invoke()method.invoke(target, args) 两层反射。CGLIB 生成的 UserService$$EnhancerByCGLIB 继承目标类,方法调用走 MethodInterceptor.intercept(this, method, args, methodProxy);关键在于它为每个方法生成两个 MethodProxy(一个走拦截器、一个走父类原始方法),并配合 FastClass 索引数组,用 methodProxy.invokeSuper(proxy, args) 按 index 直接调用父类方法,而非反射。这里必须用 invokeSuper 而不是 method.invoke,因为 method.invoke(proxy) 会再次触发代理子类的重写方法导致无限递归,而 invokeSuper 直接调父类原始实现、只织入一次。

底层深入(5-10min)

JDK 动态代理:JdkDynamicAopProxy.invoke

基于接口的代理。JDK 生成的 $Proxy0 继承 java.lang.reflect.Proxy 并实现目标接口,所有方法调用最终都会委托给 JdkDynamicAopProxy(它同时实现了 InvocationHandler)。下面是 Spring spring-aop 模块的真实源码:

// org.springframework.aop.framework.JdkDynamicAopProxy#invoke(真实源码)
@Override
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Object target = null;

    try {
        if (!this.cache.equalsDefined && AopUtils.isEqualsMethod(method)) {
            // The target does not implement the equals(Object) method itself.
            return equals(args[0]);
        }
        else if (!this.cache.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            // The target does not implement the hashCode() method itself.
            return hashCode();
        }
        else if (method.getDeclaringClass() == DecoratingProxy.class) {
            // There is only getDecoratedClass() declared -> dispatch to proxy config.
            return AopProxyUtils.ultimateTargetClass(this.advised);
        }
        else if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface() &&
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Service invocations on ProxyConfig with the proxy config...
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;

        if (this.advised.isExposeProxy()) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        // Get as late as possible to minimize the time we "own" the target,
        // in case it comes from a pool.
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);

        // Get the interception chain for this method.
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        // Check whether we have any advice. If we don't, we can fall back on direct
        // reflective invocation of the target, and avoid creating a MethodInvocation.
        if (chain.isEmpty()) {
            // We can skip creating a MethodInvocation: just invoke the target directly
            // Note that the final invoker must be an InvokerInterceptor so we know it does
            // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
            @Nullable Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
        }
        else {
            // We need to create a method invocation...
            MethodInvocation invocation =
                    new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // Proceed to the joinpoint through the interceptor chain.
            retVal = invocation.proceed();
        }

        // Massage return value if necessary.
        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target &&
                returnType != Object.class && returnType.isInstance(proxy) &&
                !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            // Special case: it returned "this" and the return type of the method
            // is type-compatible. Note that we can't help if the target sets
            // a reference to itself in another returned object.
            retVal = proxy;
        }
        else if (retVal == null && returnType != void.class && returnType.isPrimitive()) {
            throw new AopInvocationException(
                    "Null return value from advice does not match primitive return type for: " + method);
        }
        if (COROUTINES_REACTOR_PRESENT && KotlinDetector.isSuspendingFunction(method)) {
            return COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName()) ?
                    CoroutinesUtils.asFlow(retVal) : CoroutinesUtils.awaitSingleOrNull(retVal, args[args.length - 1]);
        }
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

这段代码是 JDK 动态代理的「心脏」:proxy 是 JDK 生成的 $Proxy0 实例,method 是接口方法对应的 Method 对象。核心逻辑在 getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)——它取出该方法匹配到的拦截器链,链为空时直接 AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse) 反射调目标方法,链非空时用 ReflectiveMethodInvocation.proceed() 把 advice 串成责任链逐个执行。这印证了 JDK 代理的两大本质:method 只来自接口(只能代理接口方法),且最终落点一定是 Method.invoke 反射调用

思考:为什么 JDK 动态代理「必须基于接口」,不能直接代理一个类?

因为 JDK 代理的生成原理是:Proxy.newProxyInstance 生成一个实现了目标接口$Proxy0,它继承的是 java.lang.reflect.Proxy。Java 是单继承,$Proxy0 已经占用了「继承 Proxy」这个名额,没法再去继承你的目标类,只能通过「实现相同接口」来伪装成目标类型。所以它只能拦截到「接口里声明的方法」,目标类里接口之外的方法(比如具体实现类额外加的方法)一概代理不到。

CGLIB 代理:CglibAopProxy.buildProxy 用 Enhancer 生成子类

基于子类继承。CGLIB 不要求接口,它用 Enhancer 把目标类设为父类,用 ASM 动态生成一个继承目标类的子类字节码。下面是 Spring 真实源码中生成代理的核心方法(getProxy 入口 + buildProxy):

// org.springframework.aop.framework.CglibAopProxy#buildProxy(真实源码,含 getProxy 入口)
@Override
public Object getProxy() {
    return buildProxy(null, false);
}

@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
    return buildProxy(classLoader, false);
}

private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
    }

    try {
        Class<?> rootClass = this.advised.getTargetClass();
        Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

        Class<?> proxySuperClass = rootClass;
        if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
            proxySuperClass = rootClass.getSuperclass();
            Class<?>[] additionalInterfaces = rootClass.getInterfaces();
            for (Class<?> additionalInterface : additionalInterfaces) {
                this.advised.addInterface(additionalInterface);
            }
        }

        // Validate the class, writing log messages as necessary.
        validateClassIfNecessary(proxySuperClass, classLoader);

        // Configure CGLIB Enhancer...
        Enhancer enhancer = createEnhancer();
        if (classLoader != null) {
            enhancer.setClassLoader(classLoader);
            if (classLoader instanceof SmartClassLoader smartClassLoader &&
                    smartClassLoader.isClassReloadable(proxySuperClass)) {
                enhancer.setUseCache(false);
            }
        }
        enhancer.setSuperclass(proxySuperClass);
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setAttemptLoad(enhancer.getUseCache() && AotDetector.useGeneratedArtifacts());
        enhancer.setStrategy(KotlinDetector.isKotlinType(proxySuperClass) ?
                new ClassLoaderAwareGeneratorStrategy(classLoader) :
                new ClassLoaderAwareGeneratorStrategy(classLoader, undeclaredThrowableStrategy)
        );

        Callback[] callbacks = getCallbacks(rootClass);
        Class<?>[] types = new Class<?>[callbacks.length];
        for (int x = 0; x < types.length; x++) {
            types[x] = callbacks[x].getClass();
        }
        // fixedInterceptorMap only populated at this point, after getCallbacks call above
        ProxyCallbackFilter filter = new ProxyCallbackFilter(
                this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset);
        enhancer.setCallbackFilter(filter);
        enhancer.setCallbackTypes(types);

        // Generate the proxy class and create a proxy instance.
        // ProxyCallbackFilter has method introspection capability with Advisor access.
        try {
            return (classOnly ? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks));
        }
        finally {
            // Reduce ProxyCallbackFilter to key-only state for its class cache role
            // in the CGLIB$CALLBACK_FILTER field, not leaking any Advisor state...
            filter.advised.reduceToAdvisorKey();
        }
    }
    catch (CodeGenerationException | IllegalArgumentException ex) {
        throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
                ": Common causes of this problem include using a final class or a non-visible class",
                ex);
    }
    catch (Throwable ex) {
        // TargetSource.getTarget() failed
        throw new AopConfigException("Unexpected AOP exception", ex);
    }
}

关键在 enhancer.setSuperclass(proxySuperClass):它把目标类设为父类,CGLIB 再用 ASM 生成 UserService$$EnhancerByCGLIB 子类,这就是「基于子类继承」的直接证据。getCallbacks(rootClass) 构建回调数组(DynamicAdvisedInterceptor 排在 AOP_PROXY=0 位),ProxyCallbackFilter 决定每个方法路由到哪个回调。注意 catch 块里的异常提示「final class or a non-visible class」——正因为是子类继承,final 类无法被继承、final 方法无法被重写,所以无法代理。

思考:为什么 final 类、final 方法、private 方法都代理不了?它们卡在哪?

根子都在「子类继承」这条机制上:final 类不能被继承,子类根本造不出来;final 方法不能被重写,子类无法在方法里织入拦截逻辑;private 方法对子类不可见、也无法重写,自然拦截不到。所以 CGLIB 的边界不是「性能」,而是「继承的语法限制」——凡是 Java 不允许你重写的,CGLIB 都代理不了。

CGLIB 方法织入:DynamicAdvisedInterceptor.intercept

被代理的子类方法经 CallbackFilter 路由后,进入 DynamicAdvisedInterceptor.intercept——它是 CGLIB 侧的织入入口,签名里的 methodProxy(不是 Method)正是后面 FastClass 加速的载体:

// org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept(真实源码)
private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {

    private final AdvisedSupport advised;

    public DynamicAdvisedInterceptor(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public @Nullable Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object oldProxy = null;
        boolean setProxyContext = false;
        Object target = null;
        TargetSource targetSource = this.advised.getTargetSource();
        try {
            if (this.advised.isExposeProxy()) {
                // Make invocation available if necessary.
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
            }
            // Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
            target = targetSource.getTarget();
            Class<?> targetClass = (target != null ? target.getClass() : null);
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
            Object retVal;
            // Check whether we only have one InvokerInterceptor: that is,
            // no real advice, but just reflective invocation of the target.
            if (chain.isEmpty()) {
                // We can skip creating a MethodInvocation: just invoke the target directly.
                // Note that the final invoker must be an InvokerInterceptor, so we know
                // it does nothing but a reflective operation on the target, and no hot
                // swapping or fancy proxying.
                @Nullable Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
            }
            else {
                // We need to create a method invocation...
                retVal = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain).proceed();
            }
            return processReturnType(proxy, target, method, args, retVal);
        }
        finally {
            if (target != null && !targetSource.isStatic()) {
                targetSource.releaseTarget(target);
            }
            if (setProxyContext) {
                // Restore old proxy.
                AopContext.setCurrentProxy(oldProxy);
            }
        }
    }
}

对比 JdkDynamicAopProxy.invoke 会发现结构几乎一样——都是「取拦截器链 → 空链直接反射、非空链走 ReflectiveMethodInvocation.proceed()」,因为 AOP 的 advice 织入逻辑是两套代理共用的,真正的区别只在「怎么到达这个方法」。CGLIB 的拦截器链 proceed() 走到末端时调用的是 MethodProxy.invokeSuper,它按 FastClass 索引直接调父类原始方法、绕开反射;而 JDK 代理末端始终是 Method.invoke 反射。interceptmethodProxy 参数就是这段「非反射直调」能力的载体。

思考:既然两个 invoke/intercept 的结构几乎一样,那 JDK 和 CGLIB 真正的分水岭到底是什么?

是「最终这一脚怎么踢到目标方法上」。取拦截器链、织入 advice 这套「前戏」是 Spring AOP 共用的(所以代码长得像),但到达目标方法的「最后一跳」决定了性能与前提条件:JDK 用 Method.invoke 反射,代价是慢、收益是不依赖继承、只需接口;CGLIB 用 MethodProxy.invokeSuper 走 FastClass 索引直调,代价是不能代理 final/private,收益是快。抓住这一点,两套代理的差异就不会再记混。

MethodProxy 为什么比反射快?

CGLIB 为每个被代理方法生成两个 MethodProxy:一个代表子类的重写方法(经过拦截器),一个代表父类的原始方法(不经过拦截器)。调用父类方法时用 methodProxy.invokeSuper(proxy, args)——这不是反射!

FastClass 机制:CGLIB 为代理类和目标类各生成一个 FastClass(索引数组),每个方法有一个固定的 index。invokeSuper(proxy, args) 就是拿到目标类的 FastClass,按 index 直接调用对应方法——没有 Method.invoke() 的反射开销,比 JDK 代理快 2-3 倍。

为什么 invokeSuper 而不是 method.invoke?

// 错误: method.invoke(proxy, args)
// proxy 是代理子类 → 调子类重写后的 sayHello()
// → 子类 sayHello() 内部又调 interceptor.intercept()
// → 无限递归!

// 正确: methodProxy.invokeSuper(proxy, args)
// → 直接调父类 UserService 的原始 sayHello() 实现
// → 无递归, 只织入一次

JDK vs CGLIB 对比

JDK 动态代理CGLIB
生成方式Proxy.newProxyInstance()ASM 生成子类字节码
前提条件需要接口不需要接口
限制只能代理接口方法final 类/方法不可代理
方法调用反射 Method.invoke()FastClass 索引(非反射)
性能慢 2-3x
Spring 2.x 默认是(proxy-target-class=false

Spring Boot 2.x 强制默认 CGLIB——原因是字段注入 @Autowired UserServiceImpl 需要代理对象是 UserServiceImpl 实例(而 JDK 代理只实现了接口,不是实现类)。

思考:实际项目里到底该用哪个?为什么网上会看到「JDK 有接口就用 JDK」的老说法?

老说法对应的是 Spring 2.x/3.x 默认「有接口优先 JDK」的时代,那时 CGLIB 依赖第三方库、启动开销也更大。Spring Boot 2.x 之后默认 CGLIB,主要是因为「字段注入按实现类类型注入」太常见,JDK 代理的类型不匹配。今天的实用结论是:不用刻意选——Boot 默认就是 CGLIB;只有当你明确拿到的是接口引用、且在意代理类数量或想减少字节码生成时,才手动切回 JDK(spring.aop.proxy-target-class=false)。

代理模式 vs 装饰器模式

结构上几乎一样(都持有目标对象引用 + 实现相同接口),区别在意图

代理装饰器
目的控制访问扩展功能
调用方不知情(透明替换)知情(显式包装)
实例化代理类隐藏目标调用方创建目标传给装饰器

Spring AOP 是代理模式@Autowired 拿到的就是代理,调用方不知道目标 Bean 被代理了。Java IO 是装饰器模式new BufferedInputStream(new FileInputStream(...)),调用方知道自己包装了多层。

章末提问

1. JDK 动态代理和 CGLIB 的本质区别是什么?

结论先行:本质是「基于接口 + 反射」对「基于子类继承 + 索引直调」。

因为:JDK 代理生成 $Proxy0 实现目标接口、继承 Proxy(Java 单继承,所以只能伪装成接口类型),每次调用最终走 Method.invoke 反射;CGLIB 用 ASM 生成目标类的子类,配合 FastClass 按方法 index 直调父类方法、绕开反射。由此派生出一整套差异:JDK 必须要有接口、性能慢 2-3 倍;CGLIB 不需要接口、更快,但 final 类/方法、private 方法代理不了。

2. 为什么 CGLIB 比 JDK 代理快?

结论先行:因为 CGLIB 用 FastClass 索引直调绕开了 Method.invoke() 的反射开销。

因为Method.invoke 每次都要做方法查找、参数装箱、可访问性检查等「反射税」;而 CGLIB 为每个方法预生成固定的 index,调用时 methodProxy.invokeSuper(proxy, args) 直接按 index 定位到父类原始方法执行,省去了反射的那一套运行时开销,所以约快 2-3 倍。

3. 为什么 MethodProxy.invokeSuper 不能换成 method.invoke

结论先行:换成 method.invoke(proxy, ...) 会死循环,因为 proxy 是代理子类,会再次触发重写方法、再次进入拦截器。

因为method 对应的是「子类重写后的方法」,对 proxymethod.invoke 相当于又调用了一次「被代理过的方法」,而那个方法内部又会 intercept,无限递归。invokeSuper 则是 CGLIB 生成的另一个 MethodProxy,它指向父类原始实现,从拦截器里调它能直接落到目标方法、只织入一次。

4. Spring AOP 是代理模式还是装饰器模式?怎么区分?

结论先行:是代理模式,区分标准看「调用方知不知情」。

因为:代理模式下,@Autowired 拿到的是代理对象,调用方完全不知道自己调的是代理(透明替换),目的是「控制访问」(如加事务、加日志);装饰器模式则是调用方显式地 new BufferedInputStream(new FileInputStream(...)),自己主动一层层包装,目的是「扩展功能」。一句话:调用方不知情的是代理,知情的是装饰器。

5. 哪些方法一定不会被 Spring AOP 代理到?

结论先行final 方法、private 方法、静态方法、以及「类内部自调用」的方法,都不会被正常织入。

因为final/private 方法子类无法重写(CGLIB 的机制限制),静态方法属于类不属于实例、没有 this 可代理;「自调用」是 this.method() 直接调目标对象本身,绕过了代理对象,所以切面失效。要补这一刀:自调用可以注入自身代理(或通过 AopContext.currentProxy()),final/private 则只能改代码设计。


Share this post on:

Previous Post
Spring IoC容器——BeanDefinition到Bean实例的完整旅程
Next Post
RocketMQ延时消息的底层实现