Spring--IoC(2)

本文深入探讨Spring框架的容器扩展点,包括BeanPostProcessor和BeanFactoryPostProcessor的使用,FactoryBean的功能,以及基于注解的配置方法。此外,还介绍了如何通过编程方式注册BeanPostProcessor,组件扫描和自动检测,基于Java的配置类,以及环境抽象和事件监听器的高级应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

 

容器扩展点

BeanPostProcessor

编程式注册BeanPostProcessor实例

BeanPostProcessor实例和AOP自动代理

示例

BeanFactoryPostProcessor

FactoryBean

基于注解的配置

@Required

@Autowired

@Primary

@Qualifier

CustomAutowireConfigurer

@Resource

@PostConstruct和@PreDestroy

ClassPath扫描和受管组件

@Component及其延伸

元注解

自动检测类并注册bean定义

使用过滤器来自定义扫描

在组件中定义Bean元数据

自动检测组件命名

提供自动检查组件的Scope

JSR-330 规范注解

@Inject

@Named and @ManagedBean

基于Java 的容器配置

AnnotationConfigApplicationContext

简单构造

使用register(Class…​)编程式构建容器

开启组件扫描

AnnotationConfigWebApplicationContext

@Bean

@Configuration

组合基于Java的配置

@Import

Conditionally Include @Configuration Classes or @Bean Methods

环境抽象

bean定义的profile

@Profile

Xml定义profile

Activating a Profile

默认profile

PropertySource抽象

@PropertySource

占位符解析

注册LoadTimeWeaver

ApplicationContext的附加能力

MessageSource接口--国际化

标准和自定义事件

标准事件

自定义事件

基于注解的事件监听器

异步监听器

排序监听器

泛型事件


容器扩展点

通常,应用程序开发者,不需要继承ApplicationContext的实现类。相反,Spring IoC 容器可以通过插入特殊的集成接口来实现扩展。

BeanPostProcessor

BeanPostProcessor定义了回调方法,通过实现这个回调方法,可以提供自己的(或重写容器默认的)实例化逻辑、依赖分析逻辑等。如果想在Spring 容器完成实例化、配置和初始化bean 后,实例化一些自定义的逻辑,可以插入一个或多个BeanPostProcessor的实现。

开发者可以配置多个BeanPostProcessor 实例, 并通过设置order属性来控制这些BeanPostProcessor执行的顺序,仅BeanPostProcessor实现Ordered接口才可以设置order属性。如果编写自己的BeanPostProcessor ,也应该考虑实现Ordered 接口。

接口方法:

@Nullable
default Object postProcessAfterInitialization(Object bean,String beanName)throws BeansException
@Nullable
default Object postProcessBeforeInitialization(Object bean,String beanName)throws BeansException

BeanPostProcessor实例在bean(或对象)实例上操作。也就是说,spring ioc容器实例化一个bean实例,然后BeanPostProcessor实例执行它们的工作。

BeanPostProcessor实例的作用域是每个容器。只有在使用容器层次结构时,这才相关。如果在一个容器中定义BeanPostProcessor,则它只对该容器中的bean进行后期处理。换句话说,在一个容器中定义的bean不会由在另一个容器中定义的BeanPostProcessor进行后期处理,即使两个容器都是同一层次结构的一部分。

要更改实际的bean定义(即定义bean的蓝图),您需要使用BeanFactoryPostProcessor

BeanPostProcessor会在容器的初始化方法(InitializingBean.afterPropertiesSet() 或者任何init方法)调用前和所有初始化回调之后被调用。

BeanPostProcessor可以对bean采取任何措施,包括完全忽略回调。一个BeanPostProcessor,通常会检查回调接口或使用代理包装一个bean 。一些Spring AOP 基础设施类,为了提供包装式的代理逻辑,被实现为BeanPostProcessor。

ApplicationContext 会自动地检测所有定义在配置元文件中,并实现了BeanPostProcessor 接口的bean。该ApplicationContext注册这些bean作为后置处理器,使它们可以在bean 创建完成后被调用。bean后置处理器可以像其他bean一样被部署到容器中。

注意,当通过在配置类上使用@bean注解工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口,清楚地指示后置处理器特性。否则,ApplicationContext在完全创建之前无法按类型自动检测它。由于BeanPostProcessor需要提前实例化,以便应用于上下文中其他bean的初始化,因此这种早期类型检测非常关键。

编程式注册BeanPostProcessor实例

虽然官方推荐注册BeanPostProcessor的方法是通过ApplicationContext自动检测(如前所述),但可以使用ConfigurableBeanFactory addBeanPostProcessor 方法注册它们。当您需要在注册之前评估条件逻辑,甚至需要跨层次结构中的上下文复制bean后处理器时,这一点非常有用。但是,以编程方式添加的BeanPostProcessor实例不遵守Ordered 接口。在这里,注册的顺序决定了执行的顺序。还要注意,无论显式排序如何指定,以编程方式注册的BeanPostProcessor实例总是在通过自动检测注册的实例之前进行处理

BeanPostProcessor实例和AOP自动代理

实现BeanPostProcessor接口的类是特殊的,容器对它们的处理方式不同。所有BeanPostProcessor实例和它们直接引用的bean都在启动时实例化,作为ApplicationContext的特殊启动阶段的一部分。接下来,所有BeanPostProcessor实例都以有序方式注册,并应用于容器中的所有其他bean。由于AOP自动代理是作为BeanPostProcessor本身实现的,因此BeanPostProcessor实例或它们直接引用的bean都不符合自动代理的条件,因此,它们中没有Aspect被weave进去。

对于任何这样的bean,您都应该看到一条信息日志消息:bean somebean不适合由所有BeanPostProcessor接口处理(例如:不适合自动代理)。

如果通过使用autowiring或@Resource(可能会返回到autowiring)将bean注入到BeanPostProcessor中,spring可能会在搜索类型匹配的依赖项候选者时访问意外的bean,因此使它们不符合自动代理或其他类型的bean后置处理。
例如,如果有一个用@Resource注解的依赖项,其中字段或setter名称与bean的声明名称不直接对应,并且没有使用name属性,那么spring将访问其他bean以按类型匹配它们。

示例

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

BeanFactoryPostProcessor

BeanFactoryPostProcessor操作bean的配置元数据,也就是说,Spring 的IoC容器允许BeanFactoryPostProcessor来读取配置元数据,并可以在容器实例化任何bean ( 除了BeanFactoryPostProcessor )之前修改它.

可以设置多个BeanFactoryPostProcessor实例,如果实现了Ordered接口,则可以通过order属性按序执行。

Spring预置的BeanFactoryPostProcessor有PropertyOverrideConfigurerPropertyPlaceholderConfigurer

FactoryBean

当开发者需要向容器请求一个真实的FactoryBean 实例(而不是由它生成的bean ),且调用ApplicationContext 的getBean()方法时,在bean 的id 之前加连字符"&" 。所以对于一个给定id为myBean 的FactoryBean ,调用容器的getBean("myBean")方法返回的是FactoryBean 的产品;而调用getBean("&myBean")方法则返回FactoryBean 实例本身。

基于注解的配置

Spring 2.5 也添加了对JSR-250 注解的支持,如@Resource 、@PostConstruct 和@PreDestroy 。Spring3.0添加了对JSR-330 注解的支持,包含在javax.inject 包下,如@Inject 、@Qualifier、@Named 和@Provided等。使用这些注解也需要在Spring 容器中注册特定的BeanPostProcessor
注意: 基于注解的配置注入会在基于XML 配置注入之前执行,因此同时使用两种方式,会使后面的配置覆盖前面装配的属性

<context:annotation-config/>

隐式注册的后处理器包括AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor和前面提到的RequiredAnnotationBeanPostProcessor

@Required

@Required 注解应用于bean 属性的setter 方法。

@Autowired

可以使用@Autowired 注解到传统的setter 方法中。JSR-330的@Inject 注解可以代替以上示例中Spring 的@Autowired 注解

推荐使用@Autowired 的required属性(不是@Required )注解。一方面,required 属性表示了属性对于自动装配目的不是必需的,如果它不能被自动装配,那么属性就会被忽略

@Primary

因为通过类型的自动装配可能有多个候选者,那么在选择过程中通常是需要更多控制的。达成这个目的的一种做法是Spring 的@Primary 注解。当一个依赖有多个候选者bean 时,@Primary 指定了一个优先提供的特殊bean。

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}

    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <!-- inject any dependencies required by this bean -->
    </bean>

@Qualifier

达成更多控制目的的另一种做法是Spring的@Qualifier 注解。开发者可以用特定的参数来关联限定符的值,缩小类型的集合匹配,为每一个参数来选择一个特定的bean。
 @Qualifier可以应用于字段,构造函数参数或方法参数。

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}


public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

 <bean class="example.SimpleMovieCatalog">
    <qualifier value="main"/>

    <!-- inject any dependencies required by this bean -->
</bean>

<bean class="example.SimpleMovieCatalog">
    <qualifier value="action"/>

    <!-- inject any dependencies required by this bean -->
</bean>

<bean id="movieRecommender" class="example.MovieRecommender"/>

具有@Qualifier("main")的bean仅会注入具有相同qualifier的字段或方法参数中。

可以创建自己的自定义Qualifier注解。

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

    String value();
}


public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...
}

可以添加<qualifier/>到bean元素。

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Genre" value="Action"/>
    <!-- inject any dependencies required by this bean -->
</bean>

<bean class="example.SimpleMovieCatalog">
    <qualifier type="example.Genre" value="Comedy"/>
    <!-- inject any dependencies required by this bean -->
</bean>

自定义@Qualifier也可以不指定value

   
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}

public class MovieRecommender {

    @Autowired
    @Offline
    private MovieCatalog offlineCatalog;

    // ...
}   

自定义@Qualifier也可以定义其他属性

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

    String genre();

    Format format();
}


public class MovieRecommender {

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;

    // ...
}

Xml配置:

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Action"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

CustomAutowireConfigurer

CustomAutowireConfigurer 是一个BeanFactoryPostProcessor,允许您注册自己的自定义限定符注解类型,即使它们没有用spring的@ualifier注解进行注解

<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

 

AutowireCandidateResolver决定自动注入候选:

@Resource

Spring 也支持使用JSR-250的@Resource 注解在字段或bean属性的setter 方法上注入

@Resource使用name属性,默认情况下Spring 解析这个值作为要注入的bean的名称

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder")
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果没有明确地指定name值,那么默认的名称就从字段名称或setter 方法中派生出来。

当@Resource未指定name属性时,和@Autowired类似,会优先使用primary匹配。

@PostConstruct和@PreDestroy

ClassPath扫描和受管组件

自Spring3.0 开始,很多由Spring JavaConfig 项目提供的特性成为Spring 框架核心的一部分。这就允许开发人员使用Java 来定义bean

@Component及其延伸

  • @Repository
  • @Service
  • @Controller

元注解

Spring 提供了很多元注解。元注解就是能被应用到另一个注解上的注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {

    // ....
}

//Service注解将被像Component注解一样被对待。

元注解也可以被用于创建组合注解。例如, Spring MVC 的@RestController注解就是@Controller和@ResponseBody的组合 。
另外,组合注解可能从元注解中任意重新声明属性来允许用户自定义。这个在开发者只想暴露一个元注解的子集时会特别有用


@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

    /**
     * Alias for {@link Scope#proxyMode}.
     * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
     */
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

自动检测类并注册bean定义

需要添加@ComponentScan到自己的@Configuration类上,其中的base-package元素是这两个类的公共父类包。

@Configuration
@ComponentScan(basePackages = "org.example")

//或者 @ComponentScan("org.example")
public class AppConfig  {
    ...
}

<context:component-scan base-package="org.example"/>

<context:component-scan>隐式启用了<context:annotation-config>AutowiredAnnotationBeanPostProcessor CommonAnnotationBeanPostProcessor也被隐式启用。

开发人员可以任意选择使用逗号、分号、空格分隔的列表将每个类引人父包。

使用过滤器来自定义扫描


在@ComponentScan 注解中添加includeFilters excludeFilters 参数(或者作为component-scan元素的include-filterexclude-filter子元素),可以扩展扫描 。每个过滤器元素需要type 和expression 属性。

过滤类型示例描述
annotation (default)org.example.SomeAnnotation匹配应用了指定注解的类
assignableorg.example.SomeClass指定类和接口的超类(接口)
aspectjorg.example..*Service+Aspectj
regexorg\.example\.Default.*正则表达式匹配类名称。
customorg.example.MyTypeFilter自定义Filter的实现类

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}

 

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>

 

在组件中定义Bean元数据

@Bean

@Qualifier

自动检测组件命名

组件命名由BeanNameGenerator策略执行,默认情况,Spring的原型注解(@Component, @Repository, @Service, @Controller)的value属性提供了组件的名称。如果没有指定,则使用Java Bean规范生成。也可以指定@ComponentScannameGenerator属性,此属性为BeanNameGenerator的实现


@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    ...
}

 


<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>

提供自动检查组件的Scope

@Scope注解。默认为singleton,

也可以指定@ComponentScanscopeResolver属性,此属性为ScopeMetadataResolver的实现

@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    ...
}


<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>

JSR-330 规范注解

@Inject

@javax.inject.Inject可以代替@Autowired

@Named and @ManagedBean

@javax.inject.Named or javax.annotation.ManagedBean可以代替@Component

基于Java 的容器配置

Spring 中新的Java 配置支持的核心就是@Configuration注解的类和@Bean注解的方法。

AnnotationConfigApplicationContext

简单构造

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

AnnotationConfigApplicationContext不仅仅局限于与@Configuration类合作,任意@Component或JSR-330 注解的类都可以作为构造方法的输入

使用register(Class<?>…​)编程式构建容器

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

开启组件扫描

@Configuration注解的类上加@ComponentScan

使用scan

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

AnnotationConfigWebApplicationContext

@Bean

@Configuration

组合基于Java的配置

@Import

与<import>一样,@Import 注解允许从其他配置类中加载@Bean 的配置。

@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

Conditionally Include @Configuration Classes or @Bean Methods

@Profile

@Conditional

组合基于Java的和基于XML的配置

环境抽象

bean定义的profile

@Profile

@Profile定义仅在定义的profile中,bean生效。

@Configuration
@Profile("development")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}

支持:!,& ,| 操作符

@Profile({"p1", "p2"})   #如果p1,p2是active,则生效
@Profile({"p1", "!p2"}) #如果p1 active 或者p2不是active,则生效

Xml定义profile

<beans profile="development" ...>

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
        <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
    </jdbc:embedded-database>
</beans>

Activating a Profile

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

-Dspring.profiles.active="profile1,profile2"

默认profile

@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}

-Dspring.profiles.default=

PropertySource抽象

PropertySource 是key-value的一个简单抽象,StandardEnvironment包含2个PropertySource对象:JVM属性(System.getProperties()),系统环境变量(System.getenv())
StandardServletEnvironment变量优先级

  • 1、ServletConfig parameters (if applicable — for example, in case of a DispatcherServlet context)
  • 2、ServletContext parameters (web.xml context-param entries)
  • 3、JNDI environment variables (java:comp/env/ entries)
  • 4、JVM system properties (-D command-line arguments)
  • 5、JVM system environment (operating system environment variables)

@PropertySource

@PropertySource注解提供了一种方便的机制来将PropertySource增加到Spring的Environment中

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

任何@PropertySource中形如${...}的占位符,都可以被解析为Environment中的属性资源。

占位符解析

以前,占位符的值是只能对JVM系统属性或环境变量来解析的。如今,因为环境抽象已经继承到了容器中,很容易通过容器将占位柯:解析集成。这意味着开发者可以任意地配置占位符。

注册LoadTimeWeaver

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

 

<beans>
    <context:load-time-weaver/>
</beans>

ApplicationContext的附加能力

MessageSource接口--国际化

标准和自定义事件

标准事件

  • ContextRefreshedEvent
  • ContextStartedEvent
  • ContextStoppedEvent
  • ContextClosedEvent
  • RequestHandledEvent

自定义事件

继承ApplicationEvent

基于注解的事件监听器

@EventListener

异步监听器

@EventListener
@Async

public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}

排序监听器

@EventListener
@Order
(42)

public void processBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress...
}

 

泛型事件

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值