Spring补充(一)BeanDefinition详解

image.png

一 设计解读

  • 我们可以看到在Spring是设计中,将Bean的属性抽离共性,从而定义了BeanDefinition接口,接口作为顶层设计,它抽象出了 Bean 的属性,将其统一管理起来。
  • 通过 BeanDefinition,可以将 Bean 的属性和配置信息抽象出来,并且将 BeanDefinition 存储在 Spring 容器中,以便在需要的时候创建和管理 Bean。
  • BeanDefinition 中包含了许多属性,如 Bean 的名称、类型、作用域、构造函数参数、属性等等,这些属性可以通过 XML 配置、注解、Java 配置等方式进行配置,BeanDefinition 还可以设置生命周期回调方法,用于在 Bean 的生命周期中执行一些初始化或销毁操作。
  • 通过抽象出 BeanDefinition,Spring 将 Bean 的创建和管理逻辑与具体的实现解耦,使得用户可以更加灵活地配置 Bean,并且方便地进行单元测试、模拟等操作。

1.1 AttributeAccessor接口

我们通过类图可以看到,BeanDefinition继承了AttributeAccessor接口,而AttributeAccessor接口主要定义了属性分访问方法,包括设置,获取,移除等等方法

public interface AttributeAccessor {

    /**
* 设置属性
* @param name
* @param value
*/
    void setAttribute(String name, @Nullable Object value);

    /**
*  获取属性
* @param name
* @return
*/
    @Nullable
    Object getAttribute(String name);


    /**
* 移除属性
* @param name
* @return
*/
    @Nullable
    Object removeAttribute(String name);


    /**
* 是否包含属性
* @param name
* @return
*/
    boolean hasAttribute(String name);


    /**
* 获取属性名称
* @return
*/
    String[] attributeNames();

}

这个方法很简单,我们来看看他的实现类:AttributeAccessorSupport

1.2 AttributeAccessorSupport抽象类

  • AttributeAccessorSupport类是一个抽象类,实现了AttributeAccessor接口,里面定义了通用的方法来保存或者读取元数据的方法
  • AttributeAccessorSupport便实现了这个方法,AttributeAccessorSupport定义了一个map容器,元数据就被保存在这个map里面。

AttributeAccessorSupport

public abstract class AttributeAccessorSupport implements AttributeAccessor, Serializable {

	/** Map with String keys and Object values. */
	private final Map<String, Object> attributes = new LinkedHashMap<>();


	@Override
	public void setAttribute(String name, @Nullable Object value) {
		Assert.notNull(name, "Name must not be null");
		if (value != null) {
			this.attributes.put(name, value);
		}
		else {
			removeAttribute(name);
		}
	}

	@Override
	@Nullable
	public Object getAttribute(String name) {
		Assert.notNull(name, "Name must not be null");
		return this.attributes.get(name);
	}

    
	@Override
	@Nullable
	public Object removeAttribute(String name) {
		Assert.notNull(name, "Name must not be null");
		return this.attributes.remove(name);
	}

    
	@Override
	public boolean hasAttribute(String name) {
		Assert.notNull(name, "Name must not be null");
		return this.attributes.containsKey(name);
	}

    
	@Override
	public String[] attributeNames() {
		return StringUtils.toStringArray(this.attributes.keySet());
	}


	protected void copyAttributesFrom(AttributeAccessor source) {
		Assert.notNull(source, "Source must not be null");
		String[] attributeNames = source.attributeNames();
		for (String attributeName : attributeNames) {
			setAttribute(attributeName, source.getAttribute(attributeName));
		}
	}


	@Override
	public boolean equals(@Nullable Object other) {
		return (this == other || (other instanceof AttributeAccessorSupport &&
				this.attributes.equals(((AttributeAccessorSupport) other).attributes)));
	}

	@Override
	public int hashCode() {
		return this.attributes.hashCode();
	}

}

我们可以发现实际上他维护着一个LinkedHashMap的属性集合,但是他为啥不写在BeanDefinition接口中?

  • Spring的AttributeAccessorSupport是一个实用类,用于方便地管理对象的属性(attributes),它实现了AttributeAccessor接口,这意味着它可以处理对象的任意属性,并提供一组便捷的方法来添加、获取、删除和检查属性。
  • AttributeAccessorSupport的主要作用是提供一个可扩展的、通用的属性管理框架,使得开发人员可以轻松地为他们的对象添加属性,它还可以被子类化以提供特定领域的属性管理功能。

案例:

import org.springframework.core.AttributeAccessorSupport;

public class Person extends AttributeAccessorSupport {

    private String name;
    private int age;
    private String address;

    // constructors, getters and setters for name, age, and address

    public void setPhone(String phone) {
        setAttribute("phone", phone);
    }

    public String getPhone() {
        return (String) getAttribute("phone");
    }

    public void setEmail(String email) {
        setAttribute("email", email);
    }

    public String getEmail() {
        return (String) getAttribute("email");
    }
}

// 测试
Person person = new Person();
person.setName("John");
person.setAge(30);
person.setAddress("123 Main St.");
person.setPhone("555-1234");
person.setEmail("john@example.com");

// 获取属性
String name = person.getName();
int age = person.getAge();
String address = person.getAddress();
String phone = person.getPhone();
String email = person.getEmail();


1.3 BeanMetadataElement接口

我们还是来看下类上的注释:接口提供了一个方法来获取Bean的源对象,这个源对象就是源文件,我是我们编写的Class文件

/**
 * 用于bean元数据的接口
 */
public interface BeanMetadataElement {


	/**
	 * 获取元数据的资源描述
	 * @return
	 */
	@Nullable
	default Object getSource() {
		return null;
	}

}

1.4 BeanDefinition接口

Spring 的 BeanDefinition 是 Spring 框架中一个非常重要的概念,它用于描述 Spring 容器中的 bean,包括 bean 的名称、类型、作用域、构造函数参数、属性等等。下面是 Spring BeanDefinition 的一些设计方案:

  • 接口设计

Spring 的 BeanDefinition 是一个接口,它定义了一系列方法用于设置和获取 bean 的属性。这种设计模式使得 Spring 可以通过实现 BeanDefinition 接口来支持不同类型的 bean 定义。

  • 属性配置

BeanDefinition 中包含了许多属性,如 bean 的名称、类型、作用域、构造函数参数、属性等等。Spring 提供了多种方式来配置这些属性,包括 XML 配置、注解、Java 配置等。

  • 延迟初始化

Spring 支持延迟初始化,即只有在需要使用 bean 的时候才会初始化它。为了实现延迟初始化,BeanDefinition 中可以设置一个 lazy-init 属性。当这个属性设置为 true 时,Spring 将会在第一次使用 bean 的时候才创建它。

  • 作用域

BeanDefinition 中包含一个 scope 属性,用于设置 bean 的作用域。Spring 支持多种作用域,包括 singleton、prototype、request、session、application 等等。不同的作用域对应着不同的生命周期,Spring 会根据不同的作用域来管理 bean 的生命周期。

  • 生命周期回调

BeanDefinition 中可以设置生命周期回调方法,这些方法会在 bean 的生命周期中被调用。Spring 提供了多种生命周期回调方法,包括 InitializingBean 和 DisposableBean 接口、@PostConstruct 和 @PreDestroy 注解等。
下面我们来看下代码中的设计,可以看到 BeanDefinition 接口提供了一系列操作 Bean 元数据的set、get方法,这些操作为 Bean 的描述定义了一套模板,具体的实现则交由子类。

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {

	// 单例、原型标识符
	String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
	String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;

    // 标识 Bean 的类别,分别对应 用户定义的 Bean、来源于配置文件的 Bean、Spring 内部的 Bean
	int ROLE_APPLICATION = 0;
	int ROLE_SUPPORT = 1;
	int ROLE_INFRASTRUCTURE = 2;

    // 设置、返回 Bean 的父类名称
	void setParentName(@Nullable String parentName);
	String getParentName();

    // 设置、返回 Bean 的 className
	void setBeanClassName(@Nullable String beanClassName);
	String getBeanClassName();

    // 设置、返回 Bean 的作用域
	void setScope(@Nullable String scope);
	String getScope();

    // 设置、返回 Bean 是否懒加载
	void setLazyInit(boolean lazyInit);
	boolean isLazyInit();
	
	// 设置、返回当前 Bean 所依赖的其它 Bean 名称。
	void setDependsOn(@Nullable String... dependsOn);
	String[] getDependsOn();
	
	// 设置、返回 Bean 是否可以自动注入。只对 @Autowired 注解有效
	void setAutowireCandidate(boolean autowireCandidate);
	boolean isAutowireCandidate();
	
	// 设置、返回当前 Bean 是否为主要候选 Bean 。
	// 当同一个接口有多个实现类时,通过该属性来配置某个 Bean 为主候选 Bean。
	void setPrimary(boolean primary);
	boolean isPrimary();

    // 设置、返回创建该 Bean 的工厂类。
	void setFactoryBeanName(@Nullable String factoryBeanName);
	String getFactoryBeanName();
	
	// 设置、返回创建该 Bean 的工厂方法
	void setFactoryMethodName(@Nullable String factoryMethodName);
	String getFactoryMethodName();
	
	// 返回该 Bean 构造方法参数值、所有属性
	ConstructorArgumentValues getConstructorArgumentValues();
	MutablePropertyValues getPropertyValues();

    // 返回该 Bean 是否是单例、是否是非单例、是否是抽象的
	boolean isSingleton();
	boolean isPrototype();
	boolean isAbstract();

    // 返回 Bean 的类别。类别对应上面的三个属性值。
	int getRole();

    ...
}

1.5 AnnotatedBeanDefinition接口

image.png
AnnotatedBeanDefinition 是 BeanDefinition 子接口之一,该接口扩展了 BeanDefinition 的功能,其用来操作注解元数据,一般情况下,通过注解方式得到的 Bean(@Component、@Bean),其 BeanDefinition 类型都是该接口的实现类。
AnnotatedBeanDefinition

public interface AnnotatedBeanDefinition extends BeanDefinition {

	// 获得当前 Bean 的注解元数据
	AnnotationMetadata getMetadata();

	// 获得当前 Bean 的工厂方法上的元数据
	MethodMetadata getFactoryMethodMetadata();
}

该接口可以返回两个元数据的类:

  • AnnotationMetadata:主要对 Bean 的注解信息进行操作,如:获取当前 Bean 标注的所有注解、判断是否包含指定注解。
  • MethodMetadata:方法的元数据类。提供获取方法名称、此方法所属类的全类名、是否是抽象方法、判断是否是静态方法、判断是否是final方法等。

1.6 AbstractBeanDefinition抽象类

AbstractBeanDefinition 是 BeanDefinition 的子抽象类,也是其他 BeanDefinition 类型的基类,其实现了接口中定义的一系列操作方法,并定义了一系列的常量属性,这些常量会直接影响到 Spring 实例化 Bean 时的策略。

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
		implements BeanDefinition, Cloneable {

    // 默认的 SCOPE,默认是单例
	public static final String SCOPE_DEFAULT = "";
	// 不进行自动装配
	public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;
    // 根据 Bean 的名字进行自动装配,byName
	public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
	// 根据 Bean 的类型进行自动装配,byType
	public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
	// 根据构造器进行自动装配
	public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;
	// 首先尝试按构造器自动装配。如果失败,再尝试使用 byType 进行自动装配。(Spring 3.0 之后已废除)
	public static final int AUTOWIRE_AUTODETECT = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT;
    // 通过依赖检查来查看 Bean 的每个属性是否都设置完成
    // 以下常量分别对应:不检查、对依赖对象检查、对基本类型,字符串和集合进行检查、对全部属性进行检查
	public static final int DEPENDENCY_CHECK_NONE = 0;
	public static final int DEPENDENCY_CHECK_OBJECTS = 1;
	public static final int DEPENDENCY_CHECK_SIMPLE = 2;
	public static final int DEPENDENCY_CHECK_ALL = 3;
	// 关闭应用上下文时需调用的方法名称
	public static final String INFER_METHOD = "(inferred)";
    // 存放 Bean 的 Class 对象
	private volatile Object beanClass;
	// Bean 的作用范围
	private String scope = SCOPE_DEFAULT;
    // 非抽象
	private boolean abstractFlag = false;
	// 非延迟加载
	private boolean lazyInit = false;
    // 默认不自动装配
	private int autowireMode = AUTOWIRE_NO;
    // 默认不依赖检查
	private int dependencyCheck = DEPENDENCY_CHECK_NONE;
	// 依赖的 Bean 列表
	private String[] dependsOn;
    // 可以作为自动装配的候选者,意味着可以自动装配到其他 Bean 的某个属性中
	private boolean autowireCandidate = true;
	// 创建当前 Bean 实例工厂类名称
	private String factoryBeanName;
    // 创建当前 Bean 实例工厂类中方法名称
	private String factoryMethodName;
	// 存储构造方法的参数
	private ConstructorArgumentValues constructorArgumentValues;
    // 存储 Bean 属性名称以及对应的值
	private MutablePropertyValues propertyValues;
    // 存储被覆盖的方法信息
	private MethodOverrides methodOverrides;
	// init、destroy 方法名称
	private String initMethodName;
	private String destroyMethodName;
    // 是否执行 init 和 destroy 方法
	private boolean enforceInitMethod = true;
	private boolean enforceDestroyMethod = true;
    // Bean 是否是用户定义的而不是应用程序本身定义的
	private boolean synthetic = false;
    // Bean 的身份类别,默认是用户定义的 Bean
	private int role = BeanDefinition.ROLE_APPLICATION;
	// Bean 的描述信息
	private String description;
	// Bean 定义的资源
	private Resource resource;
	
	...
}

以上是 AbstractBeanDefinition 中定义的一些常量和属性,该类中还有一部分是操作这些属性的 set 和 get 方法,这些方法都由子类来操作,且应用程序中真正使用的也是这些子类 BeanDefinition。
先来看 AbstractBeanDefinition 直接实现类:RootBeanDefinition、GenericBeanDefinition、ChildBeanDefinition。

  • RootBeanDefinition

RootBeanDefinition 是最常用的 BeanDefinition 实现类,用于描述一个完整的 Bean 定义,包括 Bean 的类型、构造函数参数、属性、依赖关系、作用域等。它是一个独立的 Bean 定义,不依赖于任何其他的 Bean 定义。它可以作为一个独立的 Bean 实例存在,也可以作为其他 Bean 的父 Bean 使用。

  • GenericBeanDefinition

GenericBeanDefinition 是 RootBeanDefinition 的默认实现类,它可以描述一个独立的 Bean 定义,也可以作为其他 Bean 的父 Bean 使用。它包含了 RootBeanDefinition 的所有属性和方法,并增加了一些额外的属性和方法,例如自动装配模式、是否允许循环依赖等。它提供了更多的扩展性和灵活性,可以适应更多的场景。

  • ChildBeanDefinition

ChildBeanDefinition 是一种特殊的 Bean 定义,它是对另一个 Bean 定义的继承或扩展。它的 ParentName 属性指向父 Bean 定义的名称,通过继承或者扩展父 Bean 定义,可以共享或者修改父 Bean 定义的属性和方法。ChildBeanDefinition 可以作为独立的 Bean 实例存在,也可以作为其他 Bean 的父 Bean 使用。
下面我们分别来看看

1.7 RootBeanDefinition子类

RootBeanDefinition 是 Spring 框架中常用的 BeanDefinition 实现类之一,用于描述一个独立的 Bean 定义,它是 AbstractBeanDefinition 的子类,继承了 AbstractBeanDefinition 的所有属性和方法,并添加了一些额外的属性和方法。
RootBeanDefinition

public class RootBeanDefinition extends AbstractBeanDefinition {

    // BeanDefinitionHolder 存储 Bean 的名称、别名、BeanDefinition
	private BeanDefinitionHolder decoratedDefinition;

	// AnnotatedElement 是java反射包的接口,通过它可以查看 Bean 的注解信息
	private AnnotatedElement qualifiedElement;

    // 允许缓存
	boolean allowCaching = true;
    
    // 工厂方法是否唯一
	boolean isFactoryMethodUnique = false;

	// 封装了 java.lang.reflect.Type,提供了泛型相关的操作
	volatile ResolvableType targetType;

	// 缓存 Class,表示 RootBeanDefinition 存储哪个类的信息
	volatile Class<?> resolvedTargetType;

	// 缓存工厂方法的返回类型
	volatile ResolvableType factoryMethodReturnType;

	// 这是以下四个构造方法字段的通用锁
	final Object constructorArgumentLock = new Object();
	// 用于缓存已解析的构造方法或工厂方法
	Executable resolvedConstructorOrFactoryMethod;
	// 将构造方法参数标记为已解析
	boolean constructorArgumentsResolved = false;
	// 用于缓存完全解析的构造方法参数
	Object[] resolvedConstructorArguments;
	// 缓存待解析的构造方法参数
	Object[] preparedConstructorArguments;

	// 这是以下两个后处理字段的通用锁
	final Object postProcessingLock = new Object();
	// 表明是否被 MergedBeanDefinitionPostProcessor 处理过
	boolean postProcessed = false;
	// 在生成代理的时候会使用,表明是否已经生成代理
	volatile Boolean beforeInstantiationResolved;

	// 实际缓存的类型是 Constructor、Field、Method 类型
	private Set<Member> externallyManagedConfigMembers;

	// InitializingBean中 的 init 回调函数名 afterPropertiesSet 会在这里记录,以便进行生命周期回调
	private Set<String> externallyManagedInitMethods;

	// DisposableBean 的 destroy 回调函数名 destroy 会在这里记录,以便进生命周期回调
	private Set<String> externallyManagedDestroyMethods;
}

1.8 ChildBeanDefinition子类

该类继承自 AbstractBeanDefinition。其相当于一个子类,不可以单独存在,必须依赖一个父 BeanDetintion,构造 ChildBeanDefinition 时,通过构造方法传入父 BeanDetintion 的名称或通过 setParentName 设置父名称。它可以从父类继承方法参数、属性值,并可以重写父类的方法,同时也可以增加新的属性或者方法。若重新定义 init 方法,destroy 方法或者静态工厂方法,ChildBeanDefinition 会重写父类的设置。
注意:从 Spring 2.5 开始,以编程方式注册 Bean 定义的首选方法是 GenericBeanDefinition,GenericBeanDefinition 可以有效替代 ChildBeanDefinition 的绝大分部使用场合。
ChildBeanDefinition

public class ChildBeanDefinition extends AbstractBeanDefinition {

	@Nullable
	private String parentName;


    public ChildBeanDefinition(String parentName) {
		super();
		this.parentName = parentName;
	}

}

1.9 GenericBeanDefinition子类

GenericBeanDefinition 是 Spring 2.5 以后新引入的 BeanDefinition,是 ChildBeanDefinition 更好的替代者,它同样可以通过 setParentName 方法设置父 BeanDefinition。

public class GenericBeanDefinition extends AbstractBeanDefinition {

	@Nullable
	private String parentName;

    public GenericBeanDefinition() {
		super();
	}

	/**
	 * Create a new GenericBeanDefinition as deep copy of the given
	 * bean definition.
	 * @param original the original bean definition to copy from
	 */
	public GenericBeanDefinition(BeanDefinition original) {
		super(original);
	}
}

1.10 ConfigurationClassBeanDefinition子类

ConfigurationClassBeanDefinition

private static class ConfigurationClassBeanDefinition extends RootBeanDefinition implements AnnotatedBeanDefinition {

		private final AnnotationMetadata annotationMetadata;

		private final MethodMetadata factoryMethodMetadata;

		private final String derivedBeanName;

		public ConfigurationClassBeanDefinition(
				ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String derivedBeanName) {

			this.annotationMetadata = configClass.getMetadata();
			this.factoryMethodMetadata = beanMethodMetadata;
			this.derivedBeanName = derivedBeanName;
			setResource(configClass.getResource());
			setLenientConstructorResolution(false);
		}
}

该类继承自 RootBeanDefinition ,并实现了 AnnotatedBeanDefinition 接口。这个 BeanDefinition 用来描述在标注 @Configuration 注解的类中,通过 @Bean 注解实例化的 Bean。
其功能特点如下:
1、如果 @Bean 注解没有指定 Bean 的名字,默认会用方法的名字命名 Bean。
2、标注 @Configuration 注解的类会成为一个工厂类,而标注 @Bean 注解的方法会成为工厂方法,通过工厂方法实例化 Bean,而不是直接通过构造方法初始化。
3、标注 @Bean 注解的类会使用构造方法自动装配

1.11 AnnotatedGenericBeanDefinition子类

AnnotatedGenericBeanDefinition

public class AnnotatedGenericBeanDefinition extends GenericBeanDefinition implements AnnotatedBeanDefinition {

	private final AnnotationMetadata metadata;

	@Nullable
	private MethodMetadata factoryMethodMetadata;


	/**
	 * Create a new AnnotatedGenericBeanDefinition for the given bean class.
	 * @param beanClass the loaded bean class
	 */
	public AnnotatedGenericBeanDefinition(Class<?> beanClass) {
		setBeanClass(beanClass);
		this.metadata = AnnotationMetadata.introspect(beanClass);
	}
}

该类继承自 GenericBeanDefinition ,并实现了 AnnotatedBeanDefinition 接口。这个 BeanDefinition 用来描述标注 @Configuration 注解的 Bean。

1.12 ScannedGenericBeanDefinition子类

该类继承自 GenericBeanDefinition ,并实现了 AnnotatedBeanDefinition 接口,这个 BeanDefinition 用来描述标注 @Component 注解的 Bean,其派生注解如 @Service、@Controller 也同理。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring 6中的BeanDefinition是指在Spring容器中定义和配置的一个实例化对象的元数据。它描述了要创建的对象的属性、构造函数参数和其他配置信息。 BeanDefinition包含了以下重要的属性: 1. Bean的Class:指定要创建的对象的类。 2. Bean的作用域(Scope):指定对象的生命周期管理方式,包括Singleton、Prototype、Request、Session等。 3. Bean的依赖关系:指定对象之间的依赖关系,即其他Bean定义的引用。 4. Bean的初始化和销毁方法:指定对象初始化时执行的方法和销毁时执行的方法。 5. Bean的属性值和引用:指定对象的属性值,可以是基本类型值或其他Bean的引用。 6. Bean的构造函数参数:指定实例化对象时传递给构造函数的参数。 通过配置BeanDefinitionSpring容器能够根据这些元数据来创建和管理Bean实例。在容器启动时,会解析并根据BeanDefinition来实例化对象,并进行必要的依赖注入、初始化和销毁操作。每个BeanDefinition都代表了一个独立的对象定义,通过指定不同的属性值和配置,可以创建不同的对象实例。 BeanDefinition的配置可以使用XML、注解或Java Config等方式进行。使用Spring的IoC容器可以很方便地管理和配置大量的BeanDefinition,使得开发人员能够更加灵活和高效地控制对象的创建和管理。 总之,BeanDefinitionSpring框架用于描述和配置对象实例化的元数据,通过配置BeanDefinition,可以对对象的属性、依赖关系、作用域等进行管理和配置,从而实现灵活的对象创建和管理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

长安不及十里

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

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

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

打赏作者

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

抵扣说明:

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

余额充值