Spring-事务源码解析2

上一篇文章我们介绍了事务开启注解@EnableTransactionManagement源码解析《Spring-事务源码解析1》 里面提到了2个关键组件,这里我们分析下Spring如何利用这2个组件来给Bean创建代理对象。

本篇文章我们看下当一个类里面包含了@Transactional注解,Spring如何利用上面说的2个组件来实例化Bean,如何让这个Bean带有事务功能。

下面三个部分用来介绍

1、Bean如何创建具有事务功能的代理类(这里就用到上面2个组件)
2、目标方法执行,如何被拦截事务拦截
3、完整的执行流程

一、Bean如何创建具有事务功能的代理类

1、创建目标类的代理对象

创建Bean当然是看Spring非常经典的doCreateBean方法,这里最终就得到目标类的代理对象,这中间就包含了Spring如何利用后置处理器控制"目标类代理对象"创建,如何利用切点匹配要添加通知的方法。因为doCreateBean内容有点多,这里我也就贴我认为比较重要的一部分代码。

//假设我们的业务类AService带有@Transactional注解,下面通过doCreateBean创建AService的代理对象
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {

	........1、下面一块代码就是通过反射得到AService的实例对象,这个还只是通过反射拿到了普通的实例对象不是代理对象。
	BeanWrapper instanceWrapper = null;
	if (mbd.isSingleton()) {
		instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
	}
	if (instanceWrapper == null) {
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	Object bean = instanceWrapper.getWrappedInstance();
	........

	........2、把普通实例对象封装ObjectFactory对象存入三级缓存,在AOP依赖注入和循环依赖会用到,这里我们先忽略。
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}
	........


	........3、通过AService普通实例对象生成AService的代理对象,也利用了前面提到的2个组件,这里的代理对象就包含了事务功能。逻辑都在"初始化"
	Object exposedObject = bean;
	populateBean(beanName, mbd, instanceWrapper);//属性填充
	exposedObject = initializeBean(beanName, exposedObject, mbd);//初始化
	........

	........4、利用三级缓存解决依赖注入和循环依赖,这里先忽略。
	if (earlySingletonExposure) {
		Object earlySingletonReference = getSingleton(beanName, false);
		if (earlySingletonReference != null) {
			if (exposedObject == bean) {
				exposedObject = earlySingletonReference;
			}
		}
	}
	........

	return exposedObject;//返回AService的代理对象
}

下面我们看下Bean的初始化逻辑,如何得到代理对象。

AServcie的普通实例对象初始化。
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
	
	Object wrappedBean = bean;//这里Bean还只是AService的普通实例对象

	//执行所有后置处理器的Before方法,本次忽略
	wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);

	//如果AServcie实现了InitializingBean接口,执行inint方法,本次忽略
	invokeInitMethods(beanName, wrappedBean, mbd);

	//重点:执行所有后置处理器的After方法,这里会生成AService的代理对象。
	wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

	return wrappedBean;
}

Spring里面包含了很多后置处理器,那么生成AService代理类的后置处理器,也就是我们前面提到的2个组件

1、AutoProxyRegistrar组件会往Spring容器中注册InfrastructureAdvisorAutoProxyCreator后置处理器
2、ProxyTransactionManagementConfiguration组件会往Spring容器中注册切面进去

在执行到InfrastructureAdvisorAutoProxyCreator后置处理器的After方法里面,主要是为通过调用wrapIfNecessary方法来创建AService代理对象。

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
	if (bean != null) {
		........
		return wrapIfNecessary(bean, beanName, cacheKey); //生成AService的代理对象
	}
	return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
	//根据Bean的信息,来获取AService所有的Advisor,Advisor可以近似的把他理解成切面,当然Advisor和切面不是一个东西,为了方便理解就近似把他看做是切面。
	//这里拿到的切面,是符合AService的切面,就Spring会有很多事务相关的切面,每个切面有自己定义的切点规则,比如有的切面处理@X注解的,有的切面只处理@Transactional
	//这里只返回AService符合切点规则的那个切面。为什么要拿切面,因为Spring在创建代理类的时候,就是要基于切面里面的通知,来对目标方法进行拦截。
	Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
	
	if (specificInterceptors != DO_NOT_PROXY) {
		//利用Advices来生成AService的代理对象。
		Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
		return proxy;
	}
	return bean;
}

wrapIfNecessary分为2块内容

1、拿到符合目标类的Advisor或者说符合目标类的切面
2、创建代理对象

1.1 getAdvicesAndAdvisorsForBean是如何获取符合规则的Advisor

//获取切面
@Override
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
	List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);//获取切面
	
	if (advisors.isEmpty()) {
		return DO_NOT_PROXY;
	}
	return advisors.toArray();
}
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
	//1、拿到所有事务的切面
	List<Advisor> candidateAdvisors = findCandidateAdvisors();
	//2、过滤出AService符合切点规则
	List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);

	return eligibleAdvisors;
}

上面包含2块内容,第一块是获取所有切面,第二块是过滤出符合规则的切面,下面我们分别看下如何获取所有切面。

//获取所有的Advisor,这里的Advisor就包含了,我们前面提到的组件ProxyTransactionManagementConfiguration,
//ProxyTransactionManagementConfiguration会往Spring容器里面注册3个Bean。 切面、切点、通知。
//其中切面BeanFactoryTransactionAttributeSourceAdvisor, 就是我们这里要拿到的。

public List<Advisor> findAdvisorBeans() {
	String[] advisorNames = this.cachedAdvisorBeanNames;
	if (advisorNames == null) {
		advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);
		this.cachedAdvisorBeanNames = advisorNames;
	}

	List<Advisor> advisors = new ArrayList<>();
	for (String name : advisorNames) {
		........
		advisors.add(this.beanFactory.getBean(name, Advisor.class));//通过Name获取Advisor对应的Bean,这里就是BeanFactoryTransactionAttributeSourceAdvisor
		........
	}
	return advisors;
}

上面拿到了所有切面,下面开始过滤符合当前目标类的切面

//从Advisor集合中,挑选出beanClass符合的Advisor
protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
	return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
}

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
	List<Advisor> eligibleAdvisors = new ArrayList<>();//定义符合规则的Advisor集合,同以最终返回。
	//引介切面过滤
	for (Advisor candidate : candidateAdvisors) {
		//如果Advisor是"引介切面",并且符合规则,引介切面是基于类层面判断是否符合规则
		if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
			eligibleAdvisors.add(candidate);
		}
	}
	//切点切面过滤
	boolean hasIntroductions = !eligibleAdvisors.isEmpty();
	for (Advisor candidate : candidateAdvisors) {
		if (candidate instanceof IntroductionAdvisor) {
			continue;
		}
		//进到这里说明这个Advisor是切点切面,切面切面是基于方法层面判断是否符合规则。
		if (canApply(candidate, clazz, hasIntroductions)) {
			eligibleAdvisors.add(candidate);
		}
	}
	return eligibleAdvisors;
}

canApply用来判断方法或者类是否匹配

public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
	if (advisor instanceof IntroductionAdvisor) {//引介切面类型,通过类来匹配
		return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
	} else if (advisor instanceof PointcutAdvisor) { //切点切面类型,通过方法判断
		PointcutAdvisor pca = (PointcutAdvisor) advisor;
		return canApply(pca.getPointcut(), targetClass, hasIntroductions);
	} else {
		return true;
	}
}
//通过方法层面判断,其实就是判断方法是否有@Transactional注解,当然每个切点的实现不一样,判断逻辑也会有差异
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
	MethodMatcher methodMatcher = pc.getMethodMatcher();
	if (methodMatcher == MethodMatcher.TRUE) {//判断是否是默认的
		return true;
	}

	//拿到类型
	Set<Class<?>> classes = new LinkedHashSet<>();
	if (!Proxy.isProxyClass(targetClass)) {
		classes.add(ClassUtils.getUserClass(targetClass));
	}
	classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));

	//拿到目标类
	for (Class<?> clazz : classes) {
		//获取所有方法
		Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
		for (Method method : methods) {
			//判断方法是否匹配,这里其实通过切点类判断是否方法是否包含特定注解。比如@Transactional
			if (methodMatcher.matches(method, targetClass)) {
				return true;
			}
		}
	}
	return false;
}

1.1小结

这一段我们讲了如何利用getAdvicesAndAdvisorsForBean拿到目标类的advisor,因为Spring在整个启动过程中会存在很多advisor,不是所有的advisor都能给AService来用,我们需要通过切面中的切点规则来判断是否符合规则。

1.2 拿到符合规则的advisor,生成代理对象

再回到wrapIfNecessary方法的第二块内容生成代理对象。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
	//拿到符合规则的advisor
	Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
	
	if (specificInterceptors != DO_NOT_PROXY) {
		//2、结合advisor,生成AService的代理对象。
		Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
		return proxy;
	}
	return bean;
}

protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {
	//创建ProxyFactory,用来生成代理对象
	ProxyFactory proxyFactory = new ProxyFactory();
	proxyFactory.copyFrom(this);
	//拿到切面,添加进去
	Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
	proxyFactory.addAdvisors(advisors);
	proxyFactory.setTargetSource(targetSource);
	proxyFactory.setFrozen(this.freezeProxy);
	...省略部分代理
	return proxyFactory.getProxy(getProxyClassLoader());
}

上面最终调用getProxy方法来生成代理对象,那么JDK和Cglib都实现了getProxy方法,这里我们分别看下他们如何创建对象。

1.2.1 JDK代理的getProxy方法
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
	//拿到代理类需要实现的接口
	Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);

	...省略部分代理

	//这里的this是JdkDynamicAopProxy
	//生成代理对象,代理对象需要实现proxiedInterfaces集合所有接口
	//当目标对象被调用的时候,会先进到JdkDynamicAopProxy的invoke方法里面
	return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
1.2.2 Cglib代理的getProxy方法
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
	//获取代理类需要继承的父类
	Class<?> rootClass = this.advised.getTargetClass();
	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);
		}
	}

	Enhancer enhancer = createEnhancer();
	enhancer.setSuperclass(proxySuperClass);   //代理类的父类,也就是目标类AServcie
	enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); //代理类需要实现的接口
	enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

	//添加回调方法,这些回调类都实现了MethodInterceptor接口,当AService的方法被调用时,会进到MethodInterceptor里面的intercept方法里面去
	Callback[] callbacks = getCallbacks(rootClass);
	Class<?>[] types = new Class<?>[callbacks.length];
	enhancer.setCallbackFilter(new ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
	。。。。。。省略部分代码
	return createProxyClassAndInstance(enhancer, callbacks);
}

getCallbacks会拿到很多MethodInterceptor的实现类,作为回调添加到代理类里面去,当AService有方法被调用时,就会进被MethodInterceptor的intercept方法拦截,在intercept里面调用切面里面的通知方法。这里MethodInterceptor的实现类就包含DynamicAdvisedInterceptor。

1.2小结

通过JDK和Cglib创建代理类,并添加拦截方法,在AService方法被执行时,被invoke方法或者intercept方法拦截。

三、目标方法执行,如何被拦截事务拦截

上面我们介绍了,如何创建代理对象,并且在创建代理对象时拦截目标方法,这里我们看下当目标方法被执行,在拦截里面做了什么操作。

当目标方法被调用时,会被JdkDynamicAopProxy的invoke方法拦截,或者是MethodInterceptor的intercept方法拦截,在invoke和intercept里面会通过切面拿到通知,在挨个的执行通知最后在执行目标方法

1、Jdk的invoke

@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	//获取符合目标方法的拦截器MethodInterceptor集合
	List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

	if (chain.isEmpty()) {
		Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
		retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);//如果拦截器集合为空,就直接调用目标方法
	} else {
		//生成拦截器链
		MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
		retVal = invocation.proceed();
	}
	return retVal;
}

2、Cglib的intercept方法

@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
	target = targetSource.getTarget();
	Class<?> targetClass = (target != null ? target.getClass() : null);
	//1、获取符合目标方法的拦截器MethodInterceptor集合。这里是拿切里的通知,通知是MethodInterceptor类型
	List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
	Object retVal;

	
	if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
		retVal = methodProxy.invoke(target, argsToUse);//如果拦截器集合为空,就直接调用目标方法
	} else {
		//2、生成拦截器链,使用的是责任链设计模式。在挨个调用拦截器。最后调用目标方法
		retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
	}
	retVal = processReturnType(proxy, target, method, retVal);
	return retVal;
}

关于获取拦截器集合和调用拦截器链,可以看到我的这篇文章《代理生成拦截器和调用拦截器》

3、触发事务拦截器

然后执行拦截器链中的最开始的拦截器,后面一次遍历,但是此时的拦截器就只有一个就是TransactionInterceptor事务方法拦截器

进到TransactionInterceptor就会执行TransactionInterceptor的invoke方法,下面看下invoke的流程

1、先拿到事务管理器的事务类型:PROPAGATION_REQUIRED,ISOLATION_DEFAULT;
2、在拿到事务的管理器也就是DataSourceTransactionManager

@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
      //目标类      
      Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
      //执行切面方法和目标方法
      return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
            @Override
            public Object proceedWithInvocation() throws Throwable {
                  return invocation.proceed();
            }
      });
}

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable {

      //1、事务隔离级别
      final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
      //2、事务管理器DataSourceTransactionManager
      final PlatformTransactionManager tm = determineTransactionManager(txAttr);


      //生成事务txInfo,里面包含本次事务状态,隔离级别,事务处理器,当本次事务执行完后需要关闭本次事务
      TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
      
      try {
      		//执行进入下一个链,此时只有一个也就是目标方法
            retVal = invocation.proceedWithInvocation();
      }
      catch (Throwable ex) {
            //更改txInfo里面的状态
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
      }
      finally {
            cleanupTransactionInfo(txInfo);
      }
      commitTransactionAfterReturning(txInfo);//根据状态是否commit
      return retVal;
}

总结

1、前面我们在Bean的实例化时,用到了第一个组件InfrastructureAdvisorAutoProxyCreator,用来触发wrapIfNecessary方法。
2、在wrapIfNecessary方法里面,我们利用了第二个组件ProxyTransactionManagementConfiguration生成的切面BeanFactoryTransactionAttributeSourceAdvisor,通过BeanFactoryTransactionAttributeSourceAdvisor的切点AnnotationTransactionAttributeSource,来判断AService是否符合切点规则,也就是解读AService类的方法是否包含@Transactional注解。

五、疑问

1、带有@Transaction注解的Bean实例化的时候,怎么如果判断该Bean带有@Transaction方法

1、InfrastructureAdvisorAutoProxyCreator#postProcessAfterInitialization
—>2、BeanFactoryTransactionAttributeSourceAdvisor
—>3、AnnotationTransactionAttributeSource
—>4、SpringTransactionAnnotationParser判断@Transaction

最终通过SpringTransactionAnnotationParser来判断Bean的方法是否带有@Transaction注解

拓展:
InfrastructureAdvisorAutoProxyCreator里面有个advisedBeans属性,在Bean的创建过程会判断Bean里面 是否是带有事务注解@Transaction的方法,如果有就会被加到advisedBeans里面去。

private final Map<Object, Boolean> advisedBeans = new ConcurrentHashMap<Object, Boolean>(256);

注意:
1、BeanFactoryTransactionAttributeSourceAdvisor事务增强器在advisedBeans是false的形式存在
2、transactionInterceptor也是false

2、带有@Transaction注解的代理Bean在哪个流程被创建

在执行BeanPostProcess处理器链的时候,由InfrastructureAdvisorAutoProxyCreator#postProcessAfterInitialization方法执行创建,该方法会调用wrapIfNecessary方法完成代理创建

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
	  .......
      //这里是拿到BeanFactoryTransactionAttributeSourceAdvisor也就是“事务增强器”
      Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
      if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);//key就是带有@Transaction的BeanName
            Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            return proxy;//返回代理对象
      }
      return bean;
}

3、如何创建@Transaction注解的Bean
默认是使用JDK动态代理

1、先创建ProxyFactory对象,设置属性
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTargetSource(目标对象LLServcieImpl);
proxyFactory.setInterfaces(目标接口LLServcie);
proxyFactory.addAdvisor(事务增强器BeanFactoryTransactionAttributeSourceAdvisor);
proxyFactory.getProxy(getProxyClassLoader())//调用下面的代码
//2、生成动态代理对象
proxiedInterfaces=AopProxyUtils.completeProxiedInterfaces(事务增强器, true);
//设置代理对象要实现接口和InvocationHandler对象
Proxy.newProxyInstance(classLoader, proxiedInterfaces, JdkDynamicAopProxy);

其中proxiedInterfaces包含接口

0 = {Class@3511} “interface cn.tedu.sample2.util.LLServcie”
1 = {Class@3676} “interface org.springframework.aop.SpringProxy”
2 = {Class@5780} “interface org.springframework.aop.framework.Advised”
3 = {Class@3897} “interface org.springframework.core.DecoratingProxy”

4、InfrastructureAdvisorAutoProxyCreator类的作用

1、postProcessBeforeInstantiation方法主要是针对所有要创建的Bean,判断存到advisedBeans里面是false还是true
2、postProcessAfterInitialization方法,处理带有@Transcation的Bean,创建代理Bean

六、总结

1、先通过@EnableTransactionManagement引入TransactionManagementConfigurationSelector
2、通过TransactionManagementConfigurationSelector,导入AutoProxyRegistrar,ProxyTransactionManagementConfiguration
3、AutoProxyRegistrar会向容器中注册InfrastructureAdvisorAutoProxyCreator
4、ProxyTransactionManagementConfiguration会向容器定义三个Bean:事务增强器、@Transaction注解解析器、事务方法拦截器
5、执行Bean的后置处理器时,通过InfrastructureAdvisorAutoProxyCreator的postProcessAfterInitialization方法创建代理对象
6、创建代理对象时,通过事务增强器BeanFactoryTransactionAttributeSourceAdvisor来得到代理类要实现的接口SpringProxy、Advised、DecoratingProxy,最终生成代理对象

7、当请求进来时,先进入JdkDynamicAopProxy的invoke方法
8、invoke里面会调用TransactionInterceptor的invoke方法,里面会调用invokeWithinTransaction方法
9、invokeWithinTransaction里面会在调用目标方法前开启事务,catch失败设置状态,然后finally根据状态来确认是否commit

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

信仰_273993243

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值