Dubbo学习之DubboReference

相关阅读

简介

本文基于Spring Boot 2.6.6dubbo-spring-boot-starter 3.0.6环境。

本文主要分析Dubbo中注解DubboReference的使用方式,并从源码分析其生效的实现原理;

使用

常用的使用方式有两种,下面分别介绍;

方式一

在自动注入的Bean上使用@DubboReference,表示该Bean为Dubbo Reference,示例代码如下:

@RestController
public class DemoController {

    // @Autowired可省略
    @Autowired
    @DubboReference
    private DemoService demoService;
}

方式二

官方推荐在BeanMethod上使用@DubboReference,这样可以一次定义,到处使用;
这种方式分两步:

  1. 定义DubboReference
  2. 注入DubboReference

示例代码如下:

@Configuration
public class ReferenceConfig {

    @Bean
    @DubboReference
    public ReferenceBean<DemoService> demoService() {
        return new ReferenceBean<>();
    }
}

@RestController
public class DemoController {

    @Autowired
    private DemoService demoService;
}

解析

注解DubboReference的解析由ReferenceAnnotationBeanPostProcessor完成;ReferenceAnnotationBeanPostProcessor继承自AbstractAnnotationBeanPostProcessor

  1. AbstractAnnotationBeanPostProcessor实现了支持自定义注解标注待注入依赖的功能框架,类似于AutowiredAnnotationBeanPostProcessor实现(支持@Autowired标注待注入依赖);
  2. 方式一,ReferenceAnnotationBeanPostProcessor会为标注了@DubboReferenceReferenceBean构建RootBeanDefinition;方式二,ReferenceAnnotationBeanPostProcessor会为标注了@DubboReferenceReferenceBeanRootBeanDefinition填充Dubbo相关属性;
  3. ReferenceAnnotationBeanPostProcessor(方式一)或者AutowiredAnnotationBeanPostProcessor(方式二)实现ReferenceBean的创建和注入;
  4. ReferenceBean实现了InitializingBean接口,故在实例化后会执行afterPropertiesSet方法,此方法会借助ReferenceBeanManager真正创建Dubbo Reference;

AbstractAnnotationBeanPostProcessor

AutowiredAnnotationBeanPostProcessorSpring Boot源码简析 AutowiredAnnotationBeanPostProcessor)功能非常相似,AbstractAnnotationBeanPostProcessor实现了支持自定义注解标注待注入依赖的功能框架,由子类指定自定义注解列表和查找注入依赖的细节;
接下来分析其核心方法实现;

构造方法
构造方法的参数列表表示自定义注解列表,代码如下:

public AbstractAnnotationBeanPostProcessor(Class<? extends Annotation>... annotationTypes) {
    Assert.notEmpty(annotationTypes, "The argument of annotations' types must not empty");
    // 指定支持的自定义注解列表
    this.annotationTypes = annotationTypes;
}

注意:支持的自定义注解列表不支持修改,只能在子类的构造方法中调用父类的构造方法进行设置;

findInjectionMetadata
用于解析指定Class中待注入的元数据,代码如下:

protected AnnotatedInjectionMetadata findInjectionMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    // 此处实现了缓存,若已处理则可直接返回
    AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    if (needsRefreshInjectionMetadata(metadata, clazz)) {
        synchronized (this.injectionMetadataCache) {
            // DCL
            metadata = this.injectionMetadataCache.get(cacheKey);

            if (needsRefreshInjectionMetadata(metadata, clazz)) {
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                try {
                    // 解析待注入的元数据
                    metadata = buildAnnotatedMetadata(clazz);
                    // 缓存,以便下次直接使用
                    this.injectionMetadataCache.put(cacheKey, metadata);
                } catch (NoClassDefFoundError err) {
                    throw new IllegalStateException("Failed to introspect object class [" + clazz.getName() +
                            "] for annotation metadata: could not find class that it depends on", err);
                }
            }
        }
    }
    return metadata;
}

private boolean needsRefreshInjectionMetadata(AnnotatedInjectionMetadata metadata, Class<?> clazz) {
    return (metadata == null || metadata.needsRefresh(clazz));
}

private AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class<?> beanClass) {
    // 解析标注注解的字段
    Collection<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> fieldElements = findFieldAnnotationMetadata(beanClass);
    // 解析标注注解的方法
    Collection<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> methodElements = findAnnotatedMethodMetadata(beanClass);
    return new AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);
}

private List<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> findFieldAnnotationMetadata(final Class<?> beanClass) {

    final List<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> elements = new LinkedList<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement>();

    // 循环遍历beanClass及其父类声明的所有字段,直至其父类为空或者为Object
    ReflectionUtils.doWithFields(beanClass, new ReflectionUtils.FieldCallback() {
        @Override
        public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {

            // 遍历支持的注解列表
            for (Class<? extends Annotation> annotationType : getAnnotationTypes()) {

                // 获取字段上的注解信息
                AnnotationAttributes attributes = getAnnotationAttributes(field, annotationType, getEnvironment(), true, true);

                if (attributes != null) {
                    // 存在注解信息

                    if (Modifier.isStatic(field.getModifiers())) {
                        // 不支持静态字段
                        if (logger.isWarnEnabled()) {
                            logger.warn("@" + annotationType.getName() + " is not supported on static fields: " + field);
                        }
                        return;
                    }

                    // 保存该字段信息
                    elements.add(new AnnotatedFieldElement(field, attributes));
                }
            }
        }
    });

    return elements;
}

private List<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> findAnnotatedMethodMetadata(final Class<?> beanClass) {

    final List<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> elements = new LinkedList<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement>();

    // 循环遍历beanClass及其父类声明的所有方法,直至其父类为空或者为Object
    ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
        @Override
        public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {

            // 获取原始方法
            Method bridgedMethod = findBridgedMethod(method);

            if (!isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                // 过滤桥方法
                return;
            }

            if (method.getAnnotation(Bean.class) != null) {
                // 过滤BeanMethod
                return;
            }

            // 遍历支持的注解列表
            for (Class<? extends Annotation> annotationType : getAnnotationTypes()) {

                // 获取方法上的注解信息
                AnnotationAttributes attributes = getAnnotationAttributes(bridgedMethod, annotationType, getEnvironment(), true, true);

                if (attributes != null && method.equals(ClassUtils.getMostSpecificMethod(method, beanClass))) {
                    if (Modifier.isStatic(method.getModifiers())) {
                        // 不支持静态字段
                        throw new IllegalStateException("When using @"+annotationType.getName() +" to inject interface proxy, it is not supported on static methods: "+method);
                    }
                    if (method.getParameterTypes().length != 1) {
                        // 方法参数校验失败
                        throw new IllegalStateException("When using @"+annotationType.getName() +" to inject interface proxy, the method must have only one parameter: "+method);
                    }
                    PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, beanClass);
                    // 保存该方法信息
                    elements.add(new AnnotatedMethodElement(method, pd, attributes));
                }
            }
        }
    });

    return elements;
}

postProcessMergedBeanDefinition
此方法中会解析出Class的待注入的元数据,并支持子类做进一步处理,代码如下:

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    if (beanType != null) {
        // 解析待注入的元数据
        AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
        // 注册外部管理的配置方法或者字段
        metadata.checkConfigMembers(beanDefinition);
        try {
            // 留给子类做进一步处理
            prepareInjection(metadata);
        } catch (Exception e) {
            logger.error("Prepare injection of @"+getAnnotationType().getSimpleName()+" failed", e);
        }
    }
}

postProcessPropertyValues
此方法中会查找Class的待注入的元数据(postProcessMergedBeanDefinition方法已解析并缓存,此处可直接获取缓存结果),并支持子类做进一步处理,然后就注入依赖,代码如下:

public PropertyValues postProcessPropertyValues(
        PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {

    try {
        // 查找待注入的元数据
        AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
        // 留给子类做进一步处理
        prepareInjection(metadata);
        // 注入依赖
        metadata.inject(bean, beanName, pvs);
    } catch (BeansException ex) {
        throw ex;
    } catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getSimpleName()
                + " dependencies is failed", ex);
    }
    return pvs;
}


// InjectionMetadata.java
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    Collection<InjectedElement> checkedElements = this.checkedElements;
    Collection<InjectedElement> elementsToIterate =
            (checkedElements != null ? checkedElements : this.injectedElements);
    if (!elementsToIterate.isEmpty()) {
        // 遍历待注入点
        for (InjectedElement element : elementsToIterate) {
            // 注入依赖
            element.inject(target, beanName, pvs);
        }
    }
}

AbstractAnnotationBeanPostProcessor使用内部类自定义实现了InjectionMetadataInjectionMetadata.InjectedElement,具体如下:

protected static class AnnotatedInjectionMetadata extends InjectionMetadata
protected class AnnotatedInjectElement extends InjectionMetadata.InjectedElement
    protected class AnnotatedMethodElement extends AnnotatedInjectElement
    public class AnnotatedFieldElement extends AnnotatedInjectElement

AnnotatedInjectionMetadata内部使用两个Collection保存AnnotatedMethodElementAnnotatedFieldElement
AnnotatedInjectElement重写了inject方法,代码如下:

protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
    // 从容器中找到依赖
    Object injectedObject = getInjectedObject(attributes, bean, beanName, getInjectedType(), this);
    // 注入依赖,区分字段、方法
    if (member instanceof Field) {
        Field field = (Field) member;
        ReflectionUtils.makeAccessible(field);
        field.set(bean, injectedObject);
    } else if (member instanceof Method) {
        Method method = (Method) member;
        ReflectionUtils.makeAccessible(method);
        method.invoke(bean, injectedObject);
    }
}


// AbstractAnnotationBeanPostProcessor.java
protected Object getInjectedObject(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                   AnnotatedInjectElement injectedElement) throws Exception {
    // 查找依赖的算法细节由子类实现
    return doGetInjectedBean(attributes, bean, beanName, injectedType, injectedElement);
}

AnnotatedMethodElement表示方法的依赖注入点;
AnnotatedFieldElement表示字段的依赖注入点;

ReferenceAnnotationBeanPostProcessor

由上文可知,作为AbstractAnnotationBeanPostProcessor的子类,ReferenceAnnotationBeanPostProcessor必须指定自定义注解列表和查找注入依赖的细节;

自定义注解列表
自定义注解列表是在构造方法中指定的,代码如下:

public ReferenceAnnotationBeanPostProcessor() {
    super(DubboReference.class, Reference.class, com.alibaba.dubbo.config.annotation.Reference.class);
}

查找注入依赖
根据beanName在Spring容器中查找依赖,需要确保依赖已经提前初始化完成,代码如下:

protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                   AnnotatedInjectElement injectedElement) throws Exception {

    if (injectedElement.injectedObject == null) {
        throw new IllegalStateException("The AnnotatedInjectElement of @DubboReference should be inited before injection");
    }

    // 根据beanName在Spring容器中查找依赖
    return getBeanFactory().getBean((String) injectedElement.injectedObject);
}

下面分析ReferenceAnnotationBeanPostProcessor如何解析处理不同使用方式下的@DubboReference标注的Bean的BeanDefinition

方式一

ReferenceAnnotationBeanPostProcessor实现了BeanFactoryPostProcessor接口,故在AbstractApplicationContext.invokeBeanFactoryPostProcessors方法中会执行其postProcessBeanFactory方法,其实现中相关核心代码如下:

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    String[] beanNames = beanFactory.getBeanDefinitionNames();
    // 遍历Spring容器中所有的BeanDefinition
    for (String beanName : beanNames) {
        Class<?> beanType;
        if (beanFactory.isFactoryBean(beanName)){
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
            if (isReferenceBean(beanDefinition)) {
                continue;
            }
            if (isAnnotatedReferenceBean(beanDefinition)) {
                // 方式二的处理
                processReferenceAnnotatedBeanDefinition(beanName, (AnnotatedBeanDefinition) beanDefinition);
                continue;
            }

            String beanClassName = beanDefinition.getBeanClassName();
            beanType = ClassUtils.resolveClass(beanClassName, getClassLoader());
        } else {
            beanType = beanFactory.getType(beanName);
        }
        if (beanType != null) {
            // 查找待注入的元数据
            // 方式一的处理
            AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
            try {
                // 进一步处理,构建并注册DubboReference标注的Bean的BeanDefinition
                prepareInjection(metadata);
            } catch (BeansException e) {
                throw e;
            } catch (Exception e) {
                throw new IllegalStateException("Prepare dubbo reference injection element failed", e);
            }
        }
    }
    ...
}

方式一中,标注了@DubboReference的Bean并没有BeanDefinition,所以需要在依赖注入前注册该Bean的BeanDefinition,此动作在prepareInjection方法中实现,代码如下:

protected void prepareInjection(AnnotatedInjectionMetadata metadata) throws BeansException {
    try {
        // 遍历字段注入点
        for (AnnotatedFieldElement fieldElement : metadata.getFieldElements()) {
            if (fieldElement.injectedObject != null) {
                // 已经处理过则可以跳过
                continue;
            }
            Class<?> injectedType = fieldElement.field.getType();
            AnnotationAttributes attributes = fieldElement.attributes;
            // 注册BeanDefinition
            String referenceBeanName = registerReferenceBean(fieldElement.getPropertyName(), injectedType, attributes, fieldElement.field);

            // 缓存该beanName,防止重复注册
            fieldElement.injectedObject = referenceBeanName;
            injectedFieldReferenceBeanCache.put(fieldElement, referenceBeanName);

        }

        // 遍历方法注入点
        for (AnnotatedMethodElement methodElement : metadata.getMethodElements()) {
            if (methodElement.injectedObject != null) {
                // 已经处理过则可以跳过
                continue;
            }
            Class<?> injectedType = methodElement.getInjectedType();
            AnnotationAttributes attributes = methodElement.attributes;
            // 注册BeanDefinition
            String referenceBeanName = registerReferenceBean(methodElement.getPropertyName(), injectedType, attributes, methodElement.method);

            // 缓存该beanName,防止重复注册
            methodElement.injectedObject = referenceBeanName;
            injectedMethodReferenceBeanCache.put(methodElement, referenceBeanName);
        }
    } catch (ClassNotFoundException e) {
        throw new BeanCreationException("prepare reference annotation failed", e);
    }
}

registerReferenceBean方法中构建了标注了@DubboReference的Bean的BeanDefinition后,再注册到Spring容器中,有了BeanDefinition,那么后续就方便创建实例了;

Bean的声明信息如下:

@Autowired
@DubboReference
private DemoService demoService;

那么该Bean是由ReferenceAnnotationBeanPostProcessor还是AutowiredAnnotationBeanPostProcessor完成依赖注入呢,还是说二者都会完成注入?
postProcessMergedBeanDefinition方法中,二者都会执行metadata.checkConfigMembers(beanDefinition);,该方法代码如下:

public void checkConfigMembers(RootBeanDefinition beanDefinition) {
    Set<InjectedElement> checkedElements = new LinkedHashSet<>(this.injectedElements.size());
    for (InjectedElement element : this.injectedElements) {
        Member member = element.getMember();
        if (!beanDefinition.isExternallyManagedConfigMember(member)) {
            beanDefinition.registerExternallyManagedConfigMember(member);
            checkedElements.add(element);
        }
    }
    this.checkedElements = checkedElements;
}

二者各自维护了injectionMetadataCachebeanDefinition是同一个,metadata是各自持有的,那么谁先执行,那么谁的checkedElements就是有值的;
inject方法中,优先根据checkedElements进行处理,所以可知后执行的MergedBeanDefinitionPostProcessor因为checkedElements为空集合而不会完成注入的动作;那么问题就变成:ReferenceAnnotationBeanPostProcessorAutowiredAnnotationBeanPostProcessor,谁的postProcessMergedBeanDefinition方法先执行?
AbstractAutowireCapableBeanFactory.applyMergedBeanDefinitionPostProcessors方法中,会遍历执行Spring容器中所有的MergedBeanDefinitionPostProcessor,代码如下:

protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
    for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) {
        processor.postProcessMergedBeanDefinition(mbd, beanType, beanName);
    }
}

mergedDefinition中的顺序来自于Spring容器中BeanPostProcessor的添加顺序,到此,问题就变成:ReferenceAnnotationBeanPostProcessorAutowiredAnnotationBeanPostProcessor谁先添加到Spring容器?

  1. ReferenceAnnotationBeanPostProcessor是在DubboInfraBeanRegisterPostProcessor.postProcessBeanFactory方法中添加到Spring容器的,由AbstractApplicationContext.invokeBeanFactoryPostProcessors调用;
  2. AutowiredAnnotationBeanPostProcessor是在AbstractApplicationContext.registerBeanPostProcessors方法中添加到Spring容器的;

至此,可以看出ReferenceAnnotationBeanPostProcessor早于AutowiredAnnotationBeanPostProcessor添加到容器中,故由ReferenceAnnotationBeanPostProcessor完成依赖注入;
依赖注入的动作发生在ReferenceAnnotationBeanPostProcessor.postProcessPropertyValues方法中,代码如下:

public PropertyValues postProcessPropertyValues(
        PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {

    try {
        AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
        // 确保注入前,Reference BeanDefinition已存在
        prepareInjection(metadata);
        // 注入Reference
        metadata.inject(bean, beanName, pvs);
    } catch (BeansException ex) {
        throw ex;
    } catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getSimpleName()
                + " dependencies is failed", ex);
    }
    return pvs;
}

方式二

同方式一,也是在ReferenceAnnotationBeanPostProcessor.postProcessBeanFactory方法中处理,相关核心代码如下:

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    String[] beanNames = beanFactory.getBeanDefinitionNames();
    // 遍历Spring容器中所有的BeanDefinition
    for (String beanName : beanNames) {
        Class<?> beanType;
        if (beanFactory.isFactoryBean(beanName)){
            // DubboReference由BeanMethod定义,故是FactoryBean
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
            if (isReferenceBean(beanDefinition)) {
                continue;
            }
            // BeanMethod的返回值为ReferenceBean
            if (isAnnotatedReferenceBean(beanDefinition)) {
                // 方式二的处理
                processReferenceAnnotatedBeanDefinition(beanName, (AnnotatedBeanDefinition) beanDefinition);
                // 处理完直接进行下一循环
                continue;
            }

            String beanClassName = beanDefinition.getBeanClassName();
            beanType = ClassUtils.resolveClass(beanClassName, getClassLoader());
        } else {
            beanType = beanFactory.getType(beanName);
        }
        if (beanType != null) {
            // 查找待注入的元数据
            // 方式一的处理
            AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
            try {
                // 进一步处理,构建并注册DubboReference标注的Bean的BeanDefinition
                prepareInjection(metadata);
            } catch (BeansException e) {
                throw e;
            } catch (Exception e) {
                throw new IllegalStateException("Prepare dubbo reference injection element failed", e);
            }
        }
    }
    ...
}

方式二中,Spring容器中已经存在标注了@DubboReference的Bean的BeanDefinition,所以processReferenceAnnotatedBeanDefinition方法只是对其BeanDefinition的属性做进一步处理;此时DubboReference相当于普通的Bean,由AutowiredAnnotationBeanPostProcessor完成依赖注入动作;

ReferenceBean

ReferenceBean实现了InitializingBean接口,故实例化后,会执行其afterPropertiesSet方法,代码如下:

public void afterPropertiesSet() throws Exception {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();

    Assert.notEmptyString(getId(), "The id of ReferenceBean cannot be empty");
    BeanDefinition beanDefinition = beanFactory.getBeanDefinition(getId());
    this.interfaceClass = (Class<?>) beanDefinition.getAttribute(ReferenceAttributes.INTERFACE_CLASS);
    this.interfaceName = (String) beanDefinition.getAttribute(ReferenceAttributes.INTERFACE_NAME);
    Assert.notNull(this.interfaceClass, "The interface class of ReferenceBean is not initialized");

    // 获取referenceProps
    if (beanDefinition.hasAttribute(Constants.REFERENCE_PROPS)) {
        referenceProps = (Map<String, Object>) beanDefinition.getAttribute(Constants.REFERENCE_PROPS);
    } else {
        if (beanDefinition instanceof AnnotatedBeanDefinition) {
            if (referenceProps == null) {
                referenceProps = new LinkedHashMap<>();
            }
            ReferenceBeanSupport.convertReferenceProps(referenceProps, interfaceClass);
            if (this.interfaceName == null) {
                this.interfaceName = (String) referenceProps.get(ReferenceAttributes.INTERFACE);
            }
        } else {
            propertyValues = beanDefinition.getPropertyValues();
        }
    }
    Assert.notNull(this.interfaceName, "The interface name of ReferenceBean is not initialized");

    ReferenceBeanManager referenceBeanManager = beanFactory.getBean(ReferenceBeanManager.BEAN_NAME, ReferenceBeanManager.class);
    // 借助ReferenceBeanManager完成Dubbo Reference真正创建
    referenceBeanManager.addReference(this);
}

至此,@DubboReference的源码分析就结束啦,Dubbo Reference的创建请见下文分析。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值