找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

862

积分

0

好友

108

主题
发表于 前天 08:53 | 查看: 5| 回复: 0

你有没有遇到过这样的问题?明明给Spring Bean加了@Transactional注解,事务却没有生效?排查许久,最后发现问题的根源竟是代理类型选择错误——JDK动态代理只代理接口方法,而你实现的方法在类上并未定义接口。本文将深入DefaultAopProxyFactory的源码,详细剖析Spring框架在“选择JDK动态代理还是CGLIB代理”背后的决策逻辑,掌握这一核心机制,未来处理代理相关问题便能游刃有余。

1. 前置知识:理解AdvisedSupport

在深入DefaultAopProxyFactory之前,必须先理解AdvisedSupport这个核心类。它是Spring AOP的配置中心,封装了创建代理对象所需的所有关键信息,包括:目标对象(target)、通知器(advisors,如@Before@After等逻辑)、要代理的接口(interfaces)、是否进行优化(optimize)、是否强制使用CGLIB代理(proxyTargetClass)等。DefaultAopProxyFactorycreateAopProxy方法,正是通过接收这个AdvisedSupport配置对象,才能智能地决策并创建相应的代理实例。

2. 核心决策逻辑:createAopProxy方法详解

DefaultAopProxyFactory的灵魂在于其createAopProxy(AdvisedSupport config)方法。下面我们通过简化后的核心源码,来厘清其决策路径:

// DefaultAopProxyFactory.java
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    // 三个关键开关:优化、强制代理类、无用户自定义接口
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("Target class must be available for creating a CGLIB proxy");
        }
        // 若目标类本身是接口或已经是JDK代理类,则回退使用JDK代理
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        // 否则,使用CGLIB代理(通过Objenesis解决无默认构造器的问题)
        return new ObjenesisCglibAopProxy(config);
    } else {
        // 三个开关均未开启,默认使用基于接口的JDK动态代理
        return new JdkDynamicAopProxy(config);
    }
}

// 判断是否“无用户自定义接口”(即接口列表为空或仅包含Spring内部定义的SpringProxy接口)
private boolean hasNoUserSuppliedInterfaces(AdvisedSupport config) {
    Class<?>[] interfaces = config.getProxiedInterfaces();
    return interfaces.length == 0 || (interfaces.length == 1 && SpringProxy.class.isAssignableFrom(interfaces[0]));
}

这段代码的逻辑可以清晰地拆解为两个层次:

  1. 前置条件判断:只要满足isOptimize()(优化开关)、isProxyTargetClass()(强制使用CGLIB代理)或hasNoUserSuppliedInterfaces()(无用户自定义接口)这三个条件中的任意一个,流程就会进入“CGLIB代理候选区”。
  2. 目标类类型检查:即使进入了候选区,Spring也会对targetClass进行检查。如果目标类是一个接口(例如@Service注解的类实现了某个接口)或者本身已经是一个JDK代理类(通过Proxy.isProxyClass()判断),Spring会“回退”到使用JDK动态代理。只有当目标类是一个普通的、非接口的类时,才会最终确定使用CGLIB代理。

举个例子:即使在配置中显式设置了proxyTargetClass=true(强制代理类),但如果目标对象是一个接口(例如UserService接口),Spring依然会选择JDK动态代理——因为CGLIB无法继承一个接口。

3. 分道扬镳:JDK与CGLIB代理的实现细节

理解了“如何选择”,我们再来看“如何创建”。Spring为两种代理方式分别提供了JdkDynamicAopProxy(JDK代理)和ObjenesisCglibAopProxy(CGLIB代理)的实现。

3.1 JDK动态代理:基于InvocationHandler

JDK动态代理要求目标类必须实现至少一个接口。代理类会实现这些接口,并通过InvocationHandler来拦截所有方法调用。JdkDynamicAopProxy的核心是invoke方法:

// JdkDynamicAopProxy.java (简化版)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    TargetSource targetSource = this.advised.targetSource;
    Object target = null;
    try {
        // 处理equals、hashCode等Object类的基础方法
        if (AopUtils.isEqualsMethod(method)) return equals(args[0]);
        if (AopUtils.isHashCodeMethod(method)) return hashCode();
        // 处理Spring Advised接口的方法调用(内部管理用)
        if (method.getDeclaringClass() == Advised.class) {
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        // 获取适用于当前方法的拦截器链(通知链,如@Before + @After)
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        if (chain.isEmpty()) {
            // 无拦截链,直接反射调用目标方法
            return AopUtils.invokeJoinpointUsingReflection(target, method, args);
        } else {
            // 创建方法调用链,并依次执行通知逻辑
            MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            return invocation.proceed();
        }
    } finally {
        if (target != null) {
            targetSource.releaseTarget(target);
        }
    }
}

JDK代理的流程非常标准:拦截方法调用 → 获取配置好的拦截器链 → 沿链执行各个通知(Advice)→ 最终调用原始目标方法。其中,ReflectiveMethodInvocation.proceed()方法负责以链式方式执行通知,例如先执行@Before通知,再调用目标方法,最后执行@After通知。

3.2 CGLIB动态代理:基于MethodInterceptor

CGLIB通过继承目标类来生成子类作为代理对象(因此目标类不能是final,方法也不能是final)。Spring使用ObjenesisCglibAopProxy包装了CGLIB,其主要目的是利用Objenesis库解决CGLIB要求目标类必须有无参构造器的限制。它的核心是intercept方法(实现了MethodInterceptor接口):

// ObjenesisCglibAopProxy.java (简化版)
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    TargetSource targetSource = this.advised.targetSource;
    Object target = null;
    try {
        // 如果需要暴露代理对象到ThreadLocal(@EnableAspectJAutoProxy(exposeProxy=true)时生效)
        if (this.advised.exposeProxy) {
            AopContext.setCurrentProxy(proxy);
        }
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        // 获取拦截器链,逻辑与JDK代理一致
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        if (chain.isEmpty()) {
            // 无拦截链,使用CGLIB的MethodProxy直接调用父类(目标)方法,性能优于反射
            return methodProxy.invoke(target, args);
        } else {
            // 执行拦截链(CglibMethodInvocation是ReflectiveMethodInvocation的子类)
            MethodInvocation invocation = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy);
            return invocation.proceed();
        }
    } finally {
        if (target != null) {
            targetSource.releaseTarget(target);
        }
        if (this.advised.exposeProxy) {
            AopContext.setCurrentProxy(null);
        }
    }
}

CGLIB代理的执行流程与JDK代理在逻辑上高度一致,但存在两个关键区别:

  • 代理机制:CGLIB通过继承生成子类代理,JDK通过实现接口生成代理。
  • 方法调用:在直接调用目标方法时(无拦截链),CGLIB使用MethodProxy.invoke(),其性能通常比JDK代理使用的反射调用(AopUtils.invokeJoinpointUsingReflection)快3-5倍。

4. 核心决策流程图示

为了更直观地理解整个决策过程,可以通过下面的时序图梳理DefaultAopProxyFactory的工作流程:

DefaultAopProxyFactory决策流程时序图

5. 实战中的典型问题:为什么@Transactional会失效?

许多开发者在实际使用中遇到过“添加了@Transactional注解但事务未回滚”的问题,这很大程度上与代理机制有关,特别是在复杂的Java应用和Spring事务管理中:

  1. 目标方法是private的:无论是JDK代理还是CGLIB代理,都无法拦截private方法。JDK代理基于接口,接口中无private方法;CGLIB通过继承,子类无法覆盖父类的private方法。
  2. 内部方法调用:在同一个Service类中,方法A直接调用方法B(this.b())。此时的this是原始的目标对象,而非被Spring增强后的代理对象,因此调用不会经过代理逻辑,导致定义在方法B上的切面(如@Transactional)失效。
  3. 目标类是final的:CGLIB通过继承生成代理。如果目标类被声明为final,则无法继承,Spring将抛出Cannot proxy final class异常,导致代理创建失败。

总结

深入源码后不难发现,Spring选择代理的策略非常务实——优先尝试基于接口的JDK动态代理,如果条件不满足则降级使用基于继承的CGLIB代理。而isOptimizeproxyTargetClasshasNoUserSuppliedInterfaces这三个开关,本质上是为开发者提供的、用于“微调”或“强制”这一决策过程的手段。

掌握这个核心逻辑后,当再次面对代理相关的问题时,你可以快速聚焦于几个关键检查点:目标类是否实现了接口?全局或特定Bean的proxyTargetClass配置是什么?目标类或方法是否为final?将这些问题与DefaultAopProxyFactory的源码逻辑一一对应,绝大多数代理谜题都能迎刃而解。




上一篇:Kubernetes网络模型深度解析:Pod跨节点直连通信与Calico实现原理
下一篇:Proxmox集群存储深度评测:本地ZFS、NFS、iSCSI与Ceph选型指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-17 18:48 , Processed in 0.120630 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表