SpringBoot学习(五):自动配置的源码实现(二)Spring容器对自动配置的加载

概述

  • 在第一篇文章:SpringBoot学习(五):自动配置的源码实现(一)@EnableAutoConfiguration详解中已经介绍过@EnableAutoConfiguration注解的设计,而Spring容器对自动化配置的加载是基于@EnableAutoConfiguration注解的这个设计来实现的,接下来具体分析该过程

1. 应用程序添加@EnableAutoConfiguration注解

  • 在应用程序主类(即SpringApplication.run方法被调用的main方法所在的类)添加使用@SpringBootApplication注解,该注解包含@EnableAutoConfiguration注解或者也可以直接使用@EnableAutoConfigure注解。@EnableAutoConfigure通过@Import注解来导入AutoConfigurationImportSelector;如下为一个应用代码配置示例:

    @SpringBootApplication
    public class MyApplication {
        public static void main(String[] args) {
            SpringApplication application = new SpringApplication(MyApplication.class);
            // ... customize application settings here
            application.run(args);
        }
    }
    

     

2. ConfigurationClassPostProcessor处理@EnableAutoConfiguration注解

ConfigurationClassPostProcessor的调用

  1. BeanFactory的创建:Spring框架调用ApplicationContext.refresh方法启动Spring容器:先会创建BeanFactory,扫描包,获取使用了@Configuration或者其他配置性质的注解,如@SpringBootApplication,@Import等注解,的类,生成BeanDefinition并注册到BeanFactory中;
  2. BeanFactoryPostProcessor执行:BeanFactory后置处理器对BeanFactory中的BeanDefinition进行加工,如注解处理。对于@Configuration等属于配置性质的注解的处理,则是交给ConfigurationClassPostProcessor来完成:

    • ConfigurationClassPostProcessor为BeanFactoryPostProcessor接口的实现类:从BeanFactory获取所有创建好的BeanDefinition列表,遍历该列表并筛选出所在类使用了@Configuration,@Import等配置性质注解的BeanDefintion 

    • 使用ConfigurationClassParser的parse方法处理该类的注解,其中会创建一个类型为ConfigurationClass的对象configClass来包装当前这个配置类(应用主类就是一个配置类),使用configClass作为参数,调用doProcessConfigurationClass方法来处理该类包含的注解。其中@Import注解的处理主要在processImports方法定义。

      /**
       * Apply processing and build a complete {@link ConfigurationClass} by reading the
       * annotations, members and methods from the source class. This method can be called
       * multiple times as relevant sources are discovered.
       * @param configClass the configuration class being build
       * @param sourceClass a source class
       * @return the superclass, or {@code null} if none found or previously processed
       */
      @Nullable
      protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
      		throws IOException {
      
      	if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
      		// Recursively process any member (nested) classes first
      		processMemberClasses(configClass, sourceClass);
      	}
      
      	// Process any @PropertySource annotations
      	
      	...
      	
      	// Process any @ComponentScan annotations
      	
      	...
      
      	// Process any @Import annotations
      	processImports(configClass, sourceClass, getImports(sourceClass), true);
      
      	// Process any @ImportResource annotations
      	
      	...
      
          // 注意@Bean注解的方法的处理,
          // 这里针对每个方法产生MethodMetadata对象,
          // 将该对象放到configClass对象的beanMethods集合中
          // 之后在使用ConfigurationClassBeanDefinitionReader来生成bean对象。
      	// Process individual @Bean methods
      	
      	...
      
      	// Process default methods on interfaces
      	
      	...
      
      	// Process superclass, if any
      	
      	...
      
      	// No superclass -> processing is complete
      	return null;
      }
      

       

@Import(AutoConfigurationImportSelector.class)注解的处理

  • @EnableAutoConfiguration内部包含@Import(AutoConfigurationImportSelector.class)注解,AutoConfigurationImportSelector为DeferredImportSelector接口的实现类,会在BeanFactory对所有BeanDefinition处理后执行来进行SpringBoot自动配置类的加载、导入,并基于@Conditional条件化配置来决定是否将该配置类内部定义的Bean注册到Spring容器。

  • @Import注解需要通过ConfigurationClassParser的processImports方法来处理。processImports方法的实现如下:

    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
    		Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
    
    	if (importCandidates.isEmpty()) {
    		return;
    	}
    
    	if (checkForCircularImports && isChainedImportOnStack(configClass)) {
    		this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    	}
    	else {
    		this.importStack.push(configClass);
    		try {
    			for (SourceClass candidate : importCandidates) {
    			
    			    // 处理ImportSelector接口的实现类
    			    // @EnableAutoConfiguration注解对应的AutoConfigurationImportSelector就是在这里处理。
    			    
    				if (candidate.isAssignable(ImportSelector.class)) {
    					// Candidate class is an ImportSelector -> delegate to it to determine imports
    					Class<?> candidateClass = candidate.loadClass();
    					ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
    					ParserStrategyUtils.invokeAwareMethods(
    							selector, this.environment, this.resourceLoader, this.registry);
    							
    				  // DeferredImportSelector:延迟导入
    					if (selector instanceof DeferredImportSelector) {
    					
        					// 延迟导入selector
        					// 通过handler方法,
        					// 存到DeferredImportSelectorGrouping这个组中,
        					// 即Group接口的实现类,
        					// 在parse方法处理完所有BeanDefintions后,再处理
        					this.deferredImportSelectorHandler.handle(
        								configClass, (DeferredImportSelector) selector);
    					}
    					else {
    						String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
    						Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
    						processImports(configClass, currentSourceClass, importSourceClasses, false);
    					}
    				}
    				
    				// 处理ImportBeanDefinitionRegistrar接口的实现类
    				
    				else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
    					// Candidate class is an ImportBeanDefinitionRegistrar ->
    					// delegate to it to register additional bean definitions
    					Class<?> candidateClass = candidate.loadClass();
    					ImportBeanDefinitionRegistrar registrar =
    							BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
    					ParserStrategyUtils.invokeAwareMethods(
    							registrar, this.environment, this.resourceLoader, this.registry);
    					configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
    				}
    				
    				// 正常使用@Configuration注解的配置类的处理
    				else {
    					// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
    					// process it as an @Configuration class
    					this.importStack.registerImport(
    							currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
    					processConfigurationClass(candidate.asConfigClass(configClass));
    				}
    			}
    		}
    	
    	    ...
    	    
    	}
    }
    

     

  • 对于@Import注解导入的类,分三种情况进行处理:

    • 实现了ImportSelector接口的类,其中AutoConfigurationImportSelector类的处理属于这种类型。

    • 由源码可知,由于AutoConfigurationImportSelector为DeferredImportSelector接口的实现类,故将AutoConfigurationImportSelector放到ConfigurationClassParser类的成员属性deferredImportSelectorHandler内部的deferredImportSelectors集合中。

    • 在ConfigurationClassParser在parse方法中,对所有BeanDefinitions处理完之后,再遍历deferredImportSelectorHandler的deferredImportSelectors集合,通过deferredImportSelector来获取SpringBoot自动配置类列表并处理;

  • 实现了ImportBeanDefinitionRegistrar接口的类,如在mybatis框架中@MapperScan的注解处理器MapperScannerRegistrar就是实现了ImportBeanDefinitionRegistrar接口;
  • 使用@Configuration注解的类,其中应用代码的配置类和AutoConfigurationImportSelector导入的每个自动配置类都是在这里处理,其处理逻辑主要在processConfigurationClass方法定义:
    • ConfigurationClass类型对象的创建:创建一个类型为ConfigurationClass对象configurationClass来包装该配置类,其中ConfigurationClass的设计目的为:通过统一的方式来封装使用了@Configuration注解的类,从而方便内部对使用了@Configuration注解的类的处理;

    • 配置类的注解处理:将该configurationClass对象作为processConfigurationClass方法的参数,调用processConfigurationClass方法来处理该配置类所包含的注解:如@PropertySource,@ComponentScan,@Import(此处递归调用processImports)等,内部@Bean注解的方法生成beanMethod对象并放到configurationClass的beanMethods集合。并且将该configurationClass对象放到ConfigurationClassParser的成员属性configurationClasses集合,这样这个集合就包含了应用中所有的使用@Configuration注解的类对应的ConfigurationClass类型的对象;

    • @Bean方法对象创建:在ConfigurationClassPostProcessor中,将ConfigurationClassParser的configurationClasses集合交给ConfigurationClassBeanDefinitionReader,ConfigurationClassBeanDefinitionReader负责遍历configurationClasses集合,对每个configurationClass,遍历configurationClass的beanMethods集合,即针对每个@Bean注解的方法,生成对应的BeanDefinition注册到Spring容器。

3. AutoConfigurationImportSelector:自动配置类的导入

  • 由2的分析可知,AutoConfigurationImportSelector添加到了ConfigurationClassParser的成员属性deferredImportSelectorHandler的deferredImportSelectors集合中,然后在ConfigurationClassParser的parse方法处理完其他BeanDefinitions后调用,具体调用deferredImportSelectorHandler.process方法,如下:

     public void parse(Set<BeanDefinitionHolder> configCandidates) {
     
         // 处理其他使用@Configuration注解的类
         
     	for (BeanDefinitionHolder holder : configCandidates) {
     		BeanDefinition bd = holder.getBeanDefinition();
     		try {
     			if (bd instanceof AnnotatedBeanDefinition) {
     				parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
     			}
     			else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
     				parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
     			}
     			else {
     				parse(bd.getBeanClassName(), holder.getBeanName());
     			}
     		}
     		catch (BeanDefinitionStoreException ex) {
     			throw ex;
     		}
     		catch (Throwable ex) {
     			throw new BeanDefinitionStoreException(
     					"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
     		}
     	}
     	
     	
         // 延迟执行deferredImportSelector定义的类的处理
     	this.deferredImportSelectorHandler.process();
     }
    

     

  • DeferredImportSelectorHandler:ConfigurationClassParser的一个内部私有类,该类内部维护了一个类型为DeferredImportSelectorHolder的deferredImportSelectors列表。在SpringBoot中该deferredImportSelectors列表大小其实就是1,即包含一个AutoConfigurationImportSelector对象。

DeferredImportSelectorHandler的process方法:自动配置类导入的内部实现

1. 功能设计

  1. 通过DeferredImportSelector从spring-boot-autoconfigure包的META-INF/spring.factories文件中获取EnableAutoConfiguration作为key,对应的自动配置类列表,如下:

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    
    ...
    
    org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
    
    ...
    

    在spring-boot-autoconfigure项目对应的RedisAutoConfiguration如下:

    @Configuration
    @ConditionalOnClass(RedisOperations.class)
    @EnableConfigurationProperties(RedisProperties.class)
    @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
    public class RedisAutoConfiguration {
    
    	@Bean
    	@ConditionalOnMissingBean(name = "redisTemplate")
    	public RedisTemplate<Object, Object> redisTemplate(
    			RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    		RedisTemplate<Object, Object> template = new RedisTemplate<>();
    		template.setConnectionFactory(redisConnectionFactory);
    		return template;
    	}
    
    	@Bean
    	@ConditionalOnMissingBean
    	public StringRedisTemplate stringRedisTemplate(
    			RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    		StringRedisTemplate template = new StringRedisTemplate();
    		template.setConnectionFactory(redisConnectionFactory);
    		return template;
    	}
    
    }
    

     

  2. 然后遍历类名列表,加载、导入对应的配置类,相当于在应用中添加了使用@Configuration注解的配置类,然后创建Configuration类型的对象configurationClass来包装该配置类,最后将configurationClass对象交给ConfigurationClassParser的processImports方法来进行递归调用。

  3. processImports方法的作用如前文所分析:对于配置类(即不是ImportSelector接口实现类和ImportBeanDefinitionRegistrar接口实现类),调用processConfigurationClass方法处理该自动配置类上面的其他注解,并将该自动配置类内部使用@Bean注解的方法,条件化生成bean注册到Spring容器,从而可以提供特定功能组件的默认实现,实现SpringBoot的自动配置功能,即应用代码可以直接通过@Autowried注入某个功能组件,而不需要显示配置。

2. 实现

(1)DeferredImportSelectorHandler:DeferredImportSelector处理器,是使用DeferredImportSelector完成自动配置类导入的功能主类

  • DeferredImportSelectorHandler的类定义如下:
    private class DeferredImportSelectorHandler {
    
    	@Nullable
    	private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();
    
        ...
    
    	public void process() {
    		List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
    		this.deferredImportSelectors = null;
    		try {
    			if (deferredImports != null) {
    				DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
    				deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
    
    				// 遍历deferredImports集合并注册到DeferredImportSelectorGroupingHandler表示的组中
    				deferredImports.forEach(handler::register);
    				
    				// 具体为通过DeferredImportSelectorGrouping来汇总这些deferredImports
    				handler.processGroupImports();
    			}
    		}
    		finally {
    			this.deferredImportSelectors = new ArrayList<>();
    		}
    	}
    
    }
    

     

  • 主要通过DeferredImportSelectorGroupingHandler来对deferredImportSelectors集合进行包装,表示deferredImportSelectors集合的deferredImportSelector属于同一个组,然后通过processGroupImports方法来完成以上所述的功能设计。

(2)DeferredImportSelector的分组Group的体系结构设计:自底向上分析

  1. DefaultDeferredImportSelectorGroup:同一组的自动配置类相关信息的汇总,实际加载的执行器。

    private static class DefaultDeferredImportSelectorGroup implements Group {
    
    	private final List<Entry> imports = new ArrayList<>();
    
    	// 实际执行自动配置类信息加载
    	// selector.selectImports返回自动配置类的类全限定名列表
    	
    	@Override
    	public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
    		for (String importClassName : selector.selectImports(metadata)) {
    			this.imports.add(new Entry(metadata, importClassName));
    		}
    	}
    
    	@Override
    	public Iterable<Entry> selectImports() {
    		return this.imports;
    	}
    }
    
    class Entry {
    
    		private final AnnotationMetadata metadata;
    
    		private final String importClassName;
    
           ...
    }
    
    1. DeferredImportSelector.Group接口实现,包含一个Entry列表,每个Entry对应一个自动配置类,包含配置类名importClassName和注解信息metadata;

    2. process方法:调用给定的DeferredImportSelector的selectImports方法,从META-INF/spring.factories获取key为EnableAutoConfiguration对应的自动配置类(类全限定名),对每个自动配置类,创建对应的Entry实例,然后保存到imports集合。

    3. selectImports的实现:SpringBoot提供的DeferredImportSelector接口的实现类AutoConfigurationImportSelector,对selectImports方法的实现:

      @Override
      public String[] selectImports(AnnotationMetadata annotationMetadata) {
      	if (!isEnabled(annotationMetadata)) {
      		return NO_IMPORTS;
      	}
      	AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
      			.loadMetadata(this.beanClassLoader);
      			
      	// 核心实现在getAutoConfigurationEntry
      	
      	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
      			autoConfigurationMetadata, annotationMetadata);
      	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
      }
      
      /**
       * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
       * of the importing {@link Configuration @Configuration} class.
       * @param autoConfigurationMetadata the auto-configuration metadata
       * @param annotationMetadata the annotation metadata of the configuration class
       * @return the auto-configurations that should be imported
       */
      protected AutoConfigurationEntry getAutoConfigurationEntry(
      		AutoConfigurationMetadata autoConfigurationMetadata,
      		AnnotationMetadata annotationMetadata) {
      	if (!isEnabled(annotationMetadata)) {
      		return EMPTY_ENTRY;
      	}
      	AnnotationAttributes attributes = getAttributes(annotationMetadata);
      	
          // getCandidateConfigurations:
          // 这里完成从META-INF/spring.factories文件获取配置类列表
          
      	List<String> configurations = getCandidateConfigurations(annotationMetadata,
      			attributes);
      	configurations = removeDuplicates(configurations);
      	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
      	checkExcludedClasses(configurations, exclusions);
      	configurations.removeAll(exclusions);
      	configurations = filter(configurations, autoConfigurationMetadata);
      	fireAutoConfigurationImportEvents(configurations, exclusions);
      	return new AutoConfigurationEntry(configurations, exclusions);
      }
      

      getCandidateConfigurations方法的实现:使用SpringFactoriesLoader.loadFactoryNames方法,从META-INF/spring.factories文件获取key为EnableAutoConfiguration对应的配置类列表:

      /**
       * Return the auto-configuration class names that should be considered. By default
       * this method will load candidates using {@link SpringFactoriesLoader} with
       * {@link #getSpringFactoriesLoaderFactoryClass()}.
       * @param metadata the source metadata
       * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
       * attributes}
       * @return a list of candidate configurations
       */
      protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
      		AnnotationAttributes attributes) {
      	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
      			getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
      	Assert.notEmpty(configurations,
      			"No auto configuration classes found in META-INF/spring.factories. If you "
      					+ "are using a custom packaging, make sure that file is correct.");
      	return configurations;
      }
      
      /**
       * Return the class used by {@link SpringFactoriesLoader} to load configuration
       * candidates.
       * @return the factory class
       */
      protected Class<?> getSpringFactoriesLoaderFactoryClass() {
      	return EnableAutoConfiguration.class;
      }
      

       

  2. DeferredImportSelectorGrouping:同一组自动配置类信息的加载和汇总的管理器,类定义如下:

    private static class DeferredImportSelectorGrouping {
    
    	private final DeferredImportSelector.Group group;
    
    	private final List<DeferredImportSelectorHolder> deferredImports = new ArrayList<>();
    
    	...
    	
    	/**
    	 * Return the imports defined by the group.
    	 * @return each import with its associated configuration class
    	 */
    	public Iterable<Group.Entry> getImports() {
    		for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
    		
    		// 将每个DeferredImportSelector作为DefaultDeferredImportSelectorGroup的process方法的参数
    		// 在DefaultDeferredImportSelectorGroup内部完成配置类信息加载和保存汇总
    			
    			this.group.process(deferredImport.getConfigurationClass().getMetadata(),
    					deferredImport.getImportSelector());
    		}
    		return this.group.selectImports();
    	}
    }
    
    1. 包含一个DeferredImportSelector集合和DefaultDeferredImportSelectorGroup的一个引用;。
    2. getImports方法:遍历DeferredImportSelector集合,将每个DeferredImportSelector作为参数,调用DefaultDeferredImportSelectorGroup的process方法,在DefaultDeferredImportSelectorGroup内部使用DeferredImportSelector完成配置类信息的加载并保存汇总。

  3. DeferredImportSelectorGroupingHandler:包含多个自动配置类的组,通过map维护组名和组管理器DeferredImportSelectorGrouping的映射。 DeferredImportSelector的分组Group的体系结构设计的主类。
    类定义如下,核心方法为processGroupImports:

    private class DeferredImportSelectorGroupingHandler {
    
        // key为组名,value为一个组DeferredImportSelectorGrouping
    	private final Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
    
    	private final Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
    
    	public void register(DeferredImportSelectorHolder deferredImport) {
    		Class<? extends Group> group = deferredImport.getImportSelector()
    				.getImportGroup();
    		DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
    				(group != null ? group : deferredImport),
    				key -> new DeferredImportSelectorGrouping(createGroup(group)));
    		grouping.add(deferredImport);
    		this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
    				deferredImport.getConfigurationClass());
    	}
    
    	public void processGroupImports() {
    
    	   // 在处理主类的@EnableAutoConfiguration注解时,
           // 已经添加好的deferredImportSelector集合所在的组
    		for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
    
                // getImports返回的是Group.Entry列表
           	    // 即配置类的列表,
           	    // 每个Entry表示一个自动配置类,
           	    // 包含配置类的全限定名和注解信息
    			grouping.getImports().forEach(entry -> {
    
    				// 对每个自动配置类生成一个对应的ConfigurationClass对象
    				ConfigurationClass configurationClass = this.configurationClasses.get(
    						entry.getMetadata());
    				try {
    
                        // 当做一个正常的使用@Configuration注解的配置类来处理,
               			// 调用processImports方法处理该配置类的@Import注解
    					processImports(configurationClass, asSourceClass(configurationClass),
    							asSourceClasses(entry.getImportClassName()), false);
    				}
    				catch (BeanDefinitionStoreException ex) {
    					throw ex;
    				}
    				catch (Throwable ex) {
    					throw new BeanDefinitionStoreException(
    							"Failed to process import candidates for configuration class [" +
    									configurationClass.getMetadata().getClassName() + "]", ex);
    				}
    			});
    		}
    	}
    
        ...
    
    }
    
    1. grouping.getImports().forEach:遍历自动配置类列表,对每个自动配置类,跟处理应用代码中使用@Configuration注解的类一样,创建ConfigurationClass类型的configurationClass对象来包装该自动配置类,将configurationClass对象作为参数调用ConfigurationClassParser的processImports方法

    2. processImports方法:由于自动配置类就是使用@Configuration注解的类,所以直接调用ConfigurationClassParser的processConfigurationClass方法,跟ConfigurationClassPostProcessor处理应用代码中@Configuration注解的类一样,处理该自动配置类的注解,包括@PropertySource,@ComponentScan,@Import,内部方法@Bean等注解的处理,具体在前面已经描述过。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值