Spring事务源码分析:从核心组件到执行流程的全面解读

  1. Spring 声明式事务基于 AOP 实现,要么是 JDK、要么是 CGLIB。由 BeanPostProcessor 创建代理。在调用的时候过程中,动态的将原始功能和事务相关的额外功能整合在一起。
  2. Spring 事务的底层依附于 DataSource、Connection、ThreadLocal 实现,由 Connection 来控制事务的开启、回滚、提交。

事务管理核心类

PlatformTransactionManager

Spring 并不直接管理事务,而是提供了多种事务管理器 。Spring 事务管理器的接口是:PlatformTransactionManager

通过这个接口,Spring 为各个平台如:JDBC、MyBatis(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

PlatformTransactionManager 接口的具体实现如下:

此接口中定义了三个方法:

public interface PlatformTransactionManager {
    // 获得事务
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    // 提交事务
    void commit(TransactionStatus var1) throws TransactionException;
    // 回滚事务
    void rollback(TransactionStatus var1) throws TransactionException;
}

为什么要定义或者说抽象出来PlatformTransactionManager这个接口呢?

主要是因为要将事务管理行为抽象出来,然后不同的平台去实现它,这样我们可以保证提供给外部的行为不变,方便我们扩展。

TransactionDefinition

事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务,这个方法里面的参数是 TransactionDefinition 类 ,这个类就定义了一些基本的事务属性。

事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。

事务属性包含了 5 个方面:

  • 隔离级别
  • 传播行为
  • 回滚规则
  • 是否只读
  • 事务超时

TransactionDefinition 接口中定义了 5 个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等。

public interface TransactionDefinition {
    
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    int TIMEOUT_DEFAULT = -1;
    
    // 返回事务的传播行为,默认值为 REQUIRED。
    int getPropagationBehavior();
    
    // 返回事务的隔离级别,默认值是 DEFAULT
    int getIsolationLevel();
    
    // 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
    int getTimeout();
    
    // 返回是否为只读事务,默认值为 false
    boolean isReadOnly();

    @Nullable
    String getName();
}

TransactionStatus

TransactionStatus 接口用来记录事务的状态,该接口定义了一组方法,用来获取或判断事务的相应状态信息。

PlatformTransactionManager.getTransaction(…) 方法返回一个 TransactionStatus 对象。

TransactionStatus 接口内容如下

public interface TransactionStatus{
    // 是否是新的事务
    boolean isNewTransaction();
    // 是否有恢复点
    boolean hasSavepoint();
    // 设置为只回滚
    void setRollbackOnly();
    // 是否为只回滚
    boolean isRollbackOnly();
    // 是否已完成
    boolean isCompleted;
}

初始化过程

通过查看 @EnableTransactionManagement 注解的源码,如下所示:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({TransactionManagementConfigurationSelector.class})
public @interface EnableTransactionManagement {
    boolean proxyTargetClass() default false;

    // 注意这里的模式 PROXY
    AdviceMode mode() default AdviceMode. PROXY;

    int order() default Integer.MAX_VALUE;
}

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {

    protected String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                // 通过查看上面的注解,可以看到这个就是默认的模式
                return new String[]{AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()};
            case ASPECTJ:
                return new String[]{this.determineTransactionAspectClass()};
            default:
                return null;
        }
    }
 
    // 这个方法与 ASPECTJ 有关,可以不看
    private String determineTransactionAspectClass() {
        return ClassUtils.isPresent("javax.transaction.Transactional", this.getClass().getClassLoader()) ? "org.springframework.transaction.aspectj.AspectJJtaTransactionManagementConfiguration" : "org.springframework.transaction.aspectj.AspectJTransactionManagementConfiguration";
    }
}

@EnableTransactionManagement 注解是通过使用 @Import({TransactionManagementConfigurationSelector.class}) 给容器中注册一个 AutoProxyRegistrar 和 ProxyTransactionManagementConfiguration 的组件。

  1. AutoProxyRegistrar:其实就是负责事务 AOP 的 BeanPostProcessor。
  2. ProxyTransactionManagementConfiguration:事务解析器,用来解析事务属性,包括配置 AOP 的通知/拦截器,切面等。

代理创建过程

AutoProxyRegistrar

AutoProxyRegistrar 给容器中注册一个 InfrastructureAdvisorAutoProxyCreator 组件,利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用.

它会检测导入者类上的某个注解是否带有属性 modeproxyTargetClass,如果检测到这些属性,在 modePROXY 时,它会向容器注册一个自动代理创建器 InfrastructureAdvisorAutoProxyCreator

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    private final Log logger = LogFactory.getLog(getClass());

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean candidateFound = false;
        Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
        for (String annoType : annoTypes) {
            // 遍历导入者类上使用的所有注解,找到第一个带有属性 mode/proxyTargetClass 属性的注解
            AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
            if (candidate == null) {
                continue;
            }
            Object mode = candidate.get("mode");
            Object proxyTargetClass = candidate.get("proxyTargetClass");
            if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
                Boolean.class == proxyTargetClass.getClass()) {
                // 找到第一个带有属性 mode/proxyTargetClass 属性的注解
                candidateFound = true;
                if (mode == AdviceMode.PROXY) {
                    // 在 mode 属性值为 PROXY 时,向容器注册一个自动代理创建器 InfrastructureAdvisorAutoProxyCreator
                    AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                    if ((Boolean) proxyTargetClass) {
                        AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                        return;
                    }
                }
            }
        }

        if (!candidateFound && this.logger.isInfoEnabled()) {
            // 日志打印
        }
    }
}
public abstract class AopConfigUtils {

    // ...

    @Nullable
    public static BeanDefinition registerAutoProxyCreatorIfNecessary(
        BeanDefinitionRegistry registry, @Nullable Object source) {
        return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
    }
}

InfrastructureAdvisorAutoProxyCreator

它是 Spring 框架中的一个内部类,用于自动创建代理对象。在 Spring 的 AOP(面向切面编程)机制中,代理对象的创建是一个核心概念,它允许开发者通过切面(Aspect)来横切关注点(cross-cutting concerns),比如事务管理、日志记录等。

一般情况下,InfrastructureAdvisorAutoProxyCreator 是由 Spring 框架自动配置和管理的,开发者无需直接与其交互。

主要功能

它是 Spring 的 AbstractAdvisorAutoProxyCreator 的子类,其主要功能包括:

  1. 自动创建代理:当 Spring 容器中的 Bean 被标记为需要代理时(例如,Bean 实现了某些接口,或者被 @Transactional 注解标记),它会自动创建这些 Bean 的代理对象。
  2. 管理 Advisor:Advisor 是 Spring AOP 的核心概念,它包含了切面逻辑和切入点。它可以自动管理这些 Advisor,并将它们应用到目标 Bean 上。
  3. 优化性能:通过缓存和优化代理对象的创建来提高性能。它只会为需要代理的 Bean 创建代理,从而避免了不必要的开销。
工作原理
  1. BeanFactoryPostProcessor:它实现了 BeanFactoryPostProcessor 接口,因此它在 Spring 容器初始化期间会被调用。它会检查所有的 Bean,并决定哪些 Bean 需要代理。
  2. 创建代理:一旦确定需要代理的 Bean,它会使用 Spring 的代理机制(如 JDK 动态代理或 CGLIB 代理)创建代理对象。
  3. 应用 Advisor:在创建代理的过程中,它会应用相关的 Advisor,将切面逻辑应用到目标 Bean 上。
创建代理过程

InfrastructureAdvisorAutoProxyCreator 创建代理的过程和 AOP 创建代理的过程是一模一样的,创建的代码都是写在父类 AbstractAutoProxyCreator 里面。

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    // ...

	/**
	 * bean 是原始对象,beanName 是原始对象的 ID 值
	 */
	@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
            // 解决循环引用的问题,之前循环引用的处理也是可以提前通过 Lambda 创建代理对象的,如果之前已经创建过,则直接返回
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                // 真正创建代理的核心,判断是否需要代理并进行代理包装
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}
    
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        // 进行安全性的校验
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            // 这里是解决事务的情况
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// 下面这一段是创建代理的核心逻辑
        // 获取当前 Bean 的拦截器(其实就是我们的切面和额外功能)通过当前 Bean,获取能否拦截的切面
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != null) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
            // 创建代理对象
			Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
            // 返回代理对象
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}
    
    protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
			@Nullable Object[] specificInterceptors, TargetSource targetSource) {

		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
			AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
		}
		
        // 通过代理工厂创建代理对象
		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.copyFrom(this);

		if (!proxyFactory.isProxyTargetClass()) {
			if (shouldProxyTargetClass(beanClass, beanName)) {
				proxyFactory.setProxyTargetClass(true);
			}
			else {
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}

        // 为代理工厂设置额外功能
		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		proxyFactory.addAdvisors(advisors);
        // 为代理工厂设置原始对象
		proxyFactory.setTargetSource(targetSource);
		customizeProxyFactory(proxyFactory);

        // 冻结所有的配置
		proxyFactory.setFrozen(this.freezeProxy);
		if (advisorsPreFiltered()) {
			proxyFactory.setPreFiltered(true);
		}
		// 根据现有的工厂策略创建代理对象
		return proxyFactory.getProxy(getProxyClassLoader());
	}
    
    // ...
}

这里的代码我就不再详细说了,很多的东西和 AOP 创建代理的过程都是共用的。可以详看这篇文章:《揭秘AnnotationAwareAspectJAutoProxyCreator:Spring AOP自动代理的幕后英雄-CSDN博客》。

整个 Spring AOP 寻找切面、通知的过程就是 postProcessBeforeInstantiation 方法的功劳,而生成 AOP 代理对象就是 postProcessAfterInitialization 的功劳。而这些寻找切面、生成代理对象的功能其实是抽象父类 AbstractAutoProxyCreator 的功能。因此,InfrastructureAdvisorAutoProxyCreator 具备了寻找切面、通知以及生成代理对象的功能。

ProxyTransactionManagementConfiguration

这个类本身就是一个配置类,用于给容器中注册事务增强器,包括切入点的规则和额外功能。

@Configuration
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {

    // 这个方法是核心方法
    @Bean(name = {"org.springframework.transaction.config.internalTransactionAdvisor"})
    @Role(2)
    public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
        // 创建事务增强器(切面)
        BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
        // 内部维护了事务相关的属性源,向事务增强器中注入属性解析器
        advisor.setTransactionAttributeSource(this.transactionAttributeSource());
        // 注入执行事务时的拦截器,后续会依赖这个拦截器来开启、提交/回滚事务
        // 当调用拥有事务的方法时,最终会调用到此拦截器内部的 invoke 方法
        advisor.setAdvice(this.transactionInterceptor());
        if (this.enableTx != null) {
            advisor.setOrder((Integer)this.enableTx.getNumber("order"));
        }
        return advisor;
    }

    // 这个方法就是为第一个方法准备对象,作为前提条件,解析切入点,对 @Transactional 的解析
    @Bean
    @Role(2)
    public TransactionAttributeSource transactionAttributeSource() {
        // 创建事务属性源
        return new AnnotationTransactionAttributeSource();
    }

    // 这个方法就是为第一个方法准备对象,作为前提条件,创建额外功能 TransactionInterceptor
    @Bean
    @Role(2)
    public TransactionInterceptor transactionInterceptor() {
        // 创建事务拦截器
        TransactionInterceptor interceptor = new TransactionInterceptor();
        // 关联事务属性
        interceptor.setTransactionAttributeSource(this.transactionAttributeSource());
        if (this.txManager != null) {
            // 关联事务管理器
            interceptor.setTransactionManager(this.txManager);
        }
        return interceptor;
    }
}

这种切入点的表现形式?其实就是以事务属性来约束,@Transactional 注解。具体我们可以详细看一下上面的 transactionAttributeSource 方法创建的 AnnotationTransactionAttributeSource 类。

切入点处理 transactionAttributeSource
AnnotationTransactionAttributeSource

事务增强器要用事务注解的信息,AnnotationTransactionAttributeSource 解析事务注解

该事务属性源是一个全局的属性源,记录着 Spring 容器中所有的事务方法的事务信息,供事务管理器 TransactionManager 使用。

public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource implements Serializable {
    
    public AnnotationTransactionAttributeSource() {
        this(true);
    }

    public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
        // 设置注解解析器
        this.publicMethodsOnly = publicMethodsOnly;
        if (!jta12Present && !ejb3Present) {
            // 添加 Spring 的事务 @Transactional 注解解析器,这也是程序默认执行的逻辑
            this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());
        } else {
            this.annotationParsers = new LinkedHashSet(4);
            // 这个是 Spring 对单机版事务的支持
            this.annotationParsers.add(new SpringTransactionAnnotationParser());
            if (jta12Present) {
                // 这个是 Spring 对分布式事务 JTA 的支持
                this.annotationParsers.add(new JtaTransactionAnnotationParser());
            }
            if (ejb3Present) {
                // 这个是 Spring 对分布式事务 Ejb3 的支持
                this.annotationParsers.add(new Ejb3TransactionAnnotationParser());
            }
        }

    }
	// ...
}
SpringTransactionAnnotationParser

这个类用于解析 @Transactional 注解的各种事务属性,例如:隔离级别、传播行为、超时、异常等。

public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable {

    public TransactionAttribute parseTransactionAnnotation(Transactional ann) {
        return parseTransactionAnnotation(AnnotationUtils.getAnnotationAttributes(ann, false, false));
    }

    protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
        RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
		// 传播行为
        Propagation propagation = attributes.getEnum("propagation");
        rbta.setPropagationBehavior(propagation.value());
        // 隔离级别
        Isolation isolation = attributes.getEnum("isolation");
        rbta.setIsolationLevel(isolation.value());
        // 超时
        rbta.setTimeout(attributes.getNumber("timeout").intValue());
        // 只读
        rbta.setReadOnly(attributes.getBoolean("readOnly"));
        rbta.setQualifier(attributes.getString("value"));
		// 回滚策略
        List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
        for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
            rollbackRules.add(new RollbackRuleAttribute(rbRule));
        }
        for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
            rollbackRules.add(new RollbackRuleAttribute(rbRule));
        }
        for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
            rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
        }
        for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
            rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
        }
        rbta.setRollbackRules(rollbackRules);
        return rbta;
    }
    
    // ...

}
额外功能 transactionInterceptor
// 这个方法就是为第一个方法准备对象,作为前提条件,创建额外功能 TransactionInterceptor
@Bean
@Role(2)
public TransactionInterceptor transactionInterceptor() {
    // 创建事务拦截器
    TransactionInterceptor interceptor = new TransactionInterceptor();
    // 关联事务属性
    interceptor.setTransactionAttributeSource(this.transactionAttributeSource());
    if (this.txManager != null) {
        // 关联事务管理器
        interceptor.setTransactionManager(this.txManager);
    }
    return interceptor;
}

有了切面、额外功能,那么代理对象就可以正常的创建出来。

代理生效过程

代理的生效过程,我们最好就通过 Debug 的方式来学习了。

测试程序如下,完整的程序参考:《万字长文深入理解Spring事务管理:配置、实现与最佳实践_spring 事务管理器-CSDN博客》:

public class Main {

    ApplicationContext applicationContext = new AnnotationConfigApplicationContext("world.xuewei.transaction.annotation");

    @Test
    public void test() {
        ILeaderService leaderService = applicationContext.getBean(ILeaderService.class);
        Leader leader = new Leader();
        leader.setCode("1002");
        leader.setName("李四");
        leader.setSex(1);
        leader.setAge(45);
        leaderService.addLeader1(leader);
    }
}

通过之前 AOP 的学习,我们知道,执行 leaderService.addLeader1(leader); 的时候会进入代理处理的 invoke 方法中(如果是 JDK 的话)。

通过代理的程序流转,会取依次执行目标对象的所有切面,而我们事务的切面(拦截器)也在其中。

之后就会执行 TransactionInterceptor 的 invoke 方法。

TransactionInterceptor

事务拦截器 TransactionInterceptor 保存了事务属性信息,事务管理器,并且实现了 MethodInterceptor,在目标方法执行的时候执行拦截器链(事务拦截器)。

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
    
    // ...
    
    @Override
    @Nullable
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
        return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
    }

}

TransactionAspectSupport

invokeWithinTransaction 是 具体执行事务的方法,这里是整个事务生效的核心方法!

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
                                         final InvocationCallback invocation) throws Throwable {
    // 获取事务属性源对象
    TransactionAttributeSource tas = getTransactionAttributeSource();
    // 通过事务属性源对象获取到当前方法实际的事务属性信息
    final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
    // 获取配置的事务管理器对象(TransactionManager 事务管理器)
    final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    // 获取切入点的唯一标识:类名 + 方法名
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
        // 开启事务!创建事务信息 TransactionInfo
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        Object retVal = null;
        try {
            // 执行被增强的原始方法
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // 异常回滚!会获取事务管理器,执行回滚操作
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
        finally {
            // 清除事务信息,恢复线程私有的老的事务信息
            cleanupTransactionInfo(txInfo);
        }
        // 提交事务!会获取事务管理器,执行事务提交操作
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }
    else {
        // 这个分支通常是不会进入的!
        // ...
}

事务属性控制

隔离级别、超时时间、只读,这三个属性都不是 Spring 提供的,而是 JDBC 的规范决定,由各类数据库驱动去实现。

Connection connection = null;
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
connection.setNetworkTimeout();
connection.setReadOnly(true);

异常的处理是由 Spring 实现,其实也比较好实现,因为 Spring 直接就可以使用 try 来捕获,之后按照不同的策略进行处理即可。

事务的传播行为也是由 Spring 来处理,这个是要复杂些,下一篇文章,我们重点分析事务的传播行为。

  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值