Spring中的循环依赖

【Spring源码:循环依赖】一文弄懂Spring循环依赖 1. 什么是循坏依赖 很简单,其实就是互相依赖对方,比如,有一个A对象依赖了B对象,B对象又依赖了A对象。 // A依赖了B public class A{ private B b; } // B依赖了A public class B{ private A a; } 但是,在我们普通的java开发中,循坏依赖会出现问题吗? 如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖注入是很正常的事情。 比如: 这样,A,B就互相依赖上了。 但是,在Spring 阅读详情

什么是循环依赖?

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:

注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。

Spring中循环依赖场景有: 
(1)构造器的循环依赖 
(2)field属性的循环依赖。

思考:在编码的过程中,有时候会存在这样的诉求,那这时候,程序应该先创建哪个对象?

Spring怎么解决循环依赖

Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或则属性是可以延后设置的(但是构造器必须是在获取引用之前)。

那spring一个对象产生需要以下几个步骤

(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象

(2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充

(3)initializeBean:调用spring xml中的init 方法。

/**
     * 实际创建指定的bean。 此时,预创建处理已经发生,
     * 例如 检查{@code postProcessBeforeInstantiation}回调。
     * 区分默认bean实例化、使用工厂方法和自动装配构造函数。
     */
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
        // Instantiate the bean.
        BeanWrapper instanceWrapper = null;
        .....
        
        if (instanceWrapper == null) {
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        .....
        // Eagerly cache singletons to be able to resolve circular references
        // even when triggered by lifecycle interfaces like BeanFactoryAware.
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            if (logger.isDebugEnabled()) {
                logger.debug("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
            //添加到三级缓存中
            addSingletonFactory(beanName, new ObjectFactory<Object>() {
                @Override
                public Object getObject() throws BeansException {
                    return getEarlyBeanReference(beanName, mbd, bean);
                }
            });
        }
 
        // Initialize the bean instance.
        Object exposedObject = bean;
        try {
            //填充依赖的bean实例。
            populateBean(beanName, mbd, instanceWrapper);
            if (exposedObject != null) {
                //调用spring xml中的init 方法。
                exposedObject = initializeBean(beanName, exposedObject, mbd);
            }
        }
        ......
        return exposedObject;
    }

其实我们简单的思考一下就发现,出现循环依赖的问题主要在 (1)和 (2)两个步骤,也就是也就是(1)中构造器循环依赖和(2)中field循环依赖。

注:createBeanInstance(...)该步骤会调用构造方法

/**
     *使用适当的实例化策略为指定的bean创建一个新实例:
     * 工厂方法,构造函数自动装配或简单实例化。
     * @return BeanWrapper for the new instance
     */
    protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
		
        .......
        // Need to determine the constructor...(确定构造函数)
        Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
        if (ctors != null ||
                mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
                mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args))  {
            return autowireConstructor(beanName, mbd, ctors, args);
        }
 
        // No special handling: simply use no-arg constructor.(使用默认无参构造器,
        //编程时候要求尽量保留无参构造器,因为你不知道哪个框架在哪会用到)
        return instantiateBean(beanName, mbd);
    }

如何解决?

那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。

/** Cache of singleton objects: bean name --> bean instance */
    //单例对象的cache
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
 
    /** Cache of singleton factories: bean name --> ObjectFactory */
    //单例对象工厂的cache
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
 
    /** Cache of early singleton objects: bean name --> bean instance */
    //提前暴光的单例对象的Cache
    private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

步骤:

1.Spring首先从一级缓存singletonObjects中获取。

2.如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。

3.如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取.

4.如果从三级缓存中获取到就从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。

/**
     * 返回指定名称(原始)单例对象。
     * 检查已经实例化的单例,也允许对当前创建的单例对象的早期引用(用于解决循环依赖)
     * 
     * @return the registered singleton object, or {@code null} if none found
     */
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //Spring首先从一级缓存singletonObjects中获取。
        Object singletonObject = this.singletonObjects.get(beanName);
 
        //isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化
        //完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依
        //赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。)
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
 
                //尝试从二级缓存earlySingletonObjects中获取。
                singletonObject = this.earlySingletonObjects.get(beanName);
 
                //allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
                if (singletonObject == null && allowEarlyReference) {
                    //尝试从三级缓存singletonFactory.getObject()(三级缓存)获取
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
 
                    //如果从三级缓存中获取到
                    // 则从singletonFactories中移除,并放入earlySingletonObjects中。
                    // 其实也就是从三级缓存移动到了二级缓存。
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }

从三级缓存的分析来看,Spring解决循环依赖的诀窍就在于singletonFactories这个滴三级cache。这个cache的类型是ObjectFactory,定义如下:

/**
 * 定义一个可以返回对象实例的工厂
 * @param <T>
 */
public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

接口在这个方法中被引用,该接口在doCreateBean(...) 方法中 实例化步骤进行了调用

/**
     *  添加一个构建指定单例对象的单例工厂
     *  紧急注册单例对象,用于解决解决循环依赖问题
     * To be called for eager registration of singletons, e.g. to be able to
     * resolve circular references.
     */
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

这里就是解决循环依赖的关键,这段代码发生在doCreateBean(...) 方法中 createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。(简单来说,就是spring创造了一个 循环依赖的结束点标识)

怎么样的循环依赖无法处理?
(1)因为加入singletonFactories三级缓存的前提是执行了构造器来创建半成品的对象,所以构造器的循环依赖没法解决。因此,Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!

(2)spring不支持原型(prototype)bean属性注入循环依赖,不同于构造器注入循环依赖会在创建spring容器context时报错,它会在用户执行代码如context.getBean()时抛出异常。因为对于原型bean,spring容器只有在需要时才会实例化,初始化它。

因为spring容器不缓存prototype类型的bean,使得无法提前暴露出一个创建中的bean。spring容器在获取prototype类型的bean时,如果因为环的存在,检测到当前线程已经正在处理该bean时,就会抛出异常。核心代码

public abstract class AbstractBeanFactory{
    /** Names of beans that are currently in creation */
    private final ThreadLocal<Object> prototypesCurrentlyInCreation =
            new NamedThreadLocal<>("Prototype beans currently in creation");
            
    protected boolean isPrototypeCurrentlyInCreation(String beanName) {
        Object curVal = this.prototypesCurrentlyInCreation.get();
        // 如果beanName已经存在于正在处理中的prototype类型的bean集中,后面会抛出异常
        return (curVal != null &&
                (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
    }
}

【Spring】Spring 解决循环依赖的 3 种方式 原文:http://www.javastack.cn/article/2020/spring-cycle-depends-on-three-ways/ 循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错。 下面说一下Spring是如果解决循环依赖的。 第一种:构造器参数循环依赖 Spring容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中。 因此如果 阅读详情

相关推荐

iOS面试题大全(附带答案)

C语言相关面试题 1.static有什么用途? 答案:在C语言中,static主要定义全局静态变量,定义局部静态变量,定义静态函数。 static 属于静态变量,使用它修饰的变量生命周期是整个源程序。 @1.在函数体内的 static 变量的作用范围为该函数体,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值; @2.在模块内的 static 全局变量可以被模块内所有函数访问,但不能被模块外其它函数访问; @3.在模块内的 static 函数只被这一模块内的其它函数调用,这个函数的使用范围被限

IOSNF的博客 2426

Spring 循环依赖解决方案

循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如A引用B,B引用C,C引用A,则它们最终反映为一个环。结论先行:1.构造器循环依赖----初始化失败2.field属性注入循环依赖----初始化成功3.field属性注入循环依赖(prototype)----初始化失败现象总结:同样对于循环依赖的场景,构造器注入和prototype类型的属性注入都会初始化Bean失败。因为@Service默认是单例的,所以单例的属性注入是可以成功的。

sinat_34814635的博客 2万+

XTDrone——ALOAM与3D激光雷达三维SLAM(处理没有点云和坐标系报错的问题)

本文介绍了XTDrone中配置如何在三维环境中使用A-LOAM算法配合velodyneHDL32激光雷达包括依赖包安装、代码修改和PX4系统配置,以及相关报错细节的注意事项。

weixin_45390670的博客 1326

【Spring】Spring的循环依赖以及解决方案

​ Spring循环依赖指的是两个或多个Bean之间相互依赖,形成一个环状依赖的情况。通俗的说,就是A依赖B,B依赖C,C依赖A,这样就形成了一个循环依赖的环。​ Spring循环依赖通常会导致Bean无法正确地被实例化,从而导致应用程序无法正常启动或者出现异常。因此,Spring循环依赖是一种需要尽量避免的情况。

长夜烛火的博客 7995

TI IWR 6843 ISK 毫米波雷达开箱测试

TI IWR6843 ISK毫米波雷达上手体验,教程十分详细,对于新手很友好。

qq_57061492的博客 4033

TVS 和压敏电阻联合使用进行浪涌保护时,压敏电阻的压敏电压要低于TVS 的钳位电压 VC。

通过先让MOV接管,可以确保在大多数情况下,电压不会迅速上升到TVS二极管的钳位电压,从而保护TVS二极管免受持续高能量冲击的损害。总结来说,将MOV的压敏电压设置得低于TVS二极管的钳位电压,可以提供一种分级的保护机制,优化浪涌保护性能,同时保护敏感的TVS二极管不被过度的能量冲击损坏,并延长其使用寿命。MOV吸收的能量(Wmov):假设浪涌电流为10kA,MOV导通后将电压限制在300V,那么Wmov = Ipp * Vmov = 10kA * 300V = 3MW。例如,Vmov可以设定为275V。

weixin_66642980的博客 2246

Spring 如何解决循环依赖问题

在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的。 这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能够一下子思考出个中奥秘。 本文主要针对这个问题,从源码的角度对其实现原理进行讲解。 1、过程演示 关于Spring bean的创建,其本质上还是一个对象的创建,既然是对象,读者朋友一定要明白一点就是,一个完整的对象包含两部分:当前对象实例化和对象属性的实例化。 在Spring中,对象的实例化是通过反射实现的,而

CRMEB小程序商城的博客 2万+

华为OD机试 - 第k个排列 - 全排列递归(Python/JS/C/C++ 2025 C卷 100分)

给定参数n,从1到n会有n个整数:1,2,3,…,n,这n个数字共有n!按大小顺序升序列出所有排列的情况,并一一标记当n=3时,所有排列如下“123”,“132,“213”,“231”,“312”,“321”输入两行,第一行为n,第二行为k,给定n的范围是[1,9],给定k的范围是[1,n!排列为“12”和“21”,第2个是“21”。给定n和k,返回第k个排列。输出排在第k位置的数字。只有一个排列,直接返回。

学Java,找哪吒 983

Spring如何解决循环依赖

烂大街的Spring循环依赖该如何回答? 什么是循环依赖? 从字面上来理解就是A依赖B的同时B也依赖了A,就像上面这样,或者C依赖与自己本身。体现到代码层次就是这个样子 @Component public class A { // A中注入了B @Autowired private B b; } --- @Component public class B { // B中注入了A @Autowired private A a; } --- // 自己依赖自己 @Component public c

意田天的博客 2万+

【深度学习基础】多任务学习(Multi-Task Learning)

多任务学习(Multi-task learning)是和单任务学习(single-task learning)相对的一种机器学习方法。在机器学习领域,标准的算法理论是一次学习一个任务,也就是系统的输出为实数的情况。复杂的学习问题先被分解成理论上独立的子问题,然后分别对每个子问题进行学习,最后通过对子问题学习结果的组合建立复杂问题的数学模型。多任务学习是一种联合学习,多个任务并行学习,结果相互影响。一次只学习一个任务(task),大部分的机器学习任务都属于单任务学习。

十二月的猫 1万+

(五)激光线扫描-位移台标定

线激光属于主动测量方式,但是由于线激光的特性,我们只能通过提取激光中心线获取这一条线上的高度信息,那么要进行三维重建的话,就需要通过平移或者是旋转的方式,来让线激光扫描被测物体的完整轮廓,也就是整个表面。激光线的密度越高还原出来的物体越细腻,但由于数据量大,也会导致速度变慢。下面以位移台为例讲解,如何标定位移台。

FlyWine的博客 2889

spring 如何解决循环依赖

spring 如何解决循环依赖

快乐的小三菊的博客 3940

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

【Spring】Spring的循环依赖及解决方案

在Spring框架中,循环依赖是一个非常常见的问题。当两个或多个bean之间相互依赖时,就会产生循环依赖。这种情况可能会导致程序无限循环,从而导致性能问题和程序崩溃。Spring框架通过检测循环依赖并采取相应措施来解决这个问题。例如,使用懒加载的方式来避免循环依赖。但是,有时候我们的代码中可能会有一些错误,导致循环依赖的产生。在这篇博客中,我们将深入探讨Spring循环依赖的问题。我们将讨论如何产生循环依赖,以及如何避免和解决循环依赖。我们还将深入探讨Spring框架如何检测和解决循环依赖。

qq_46096136的博客 9466

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Spring中的循环依赖问题

浅谈spring中的循环依赖问题

AG.『Feng』的博客 3792

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Spring之循环依赖

Spring在Bean的实例化过程中,提供了对循环依赖的解决方案,但是这部分代码非常的生涩难懂,今天,我们就从一个Spring的源码来看看这个循环依赖到底是何方神圣! 首先Spring中的循环依赖到底是什么呢? 循环依赖就是循环引用,就是在类A中引用了类B,而类B中引用了类C,而类C中又引用了类A,如图: 图一 在图一中,我们可以看到A中存在属性B,B中存在属性C,而C中有又存在属...

Taylar_where的博客 9429

Spring循环依赖

循环依赖,其实就是循环引用,就是两个或者两个以上的 Bean 互相引用对方,最终形成一个闭环,如 A 依赖 B,B 依赖 C,C 依赖 A。结果:需要注意的是本例中启动时是不会报错的(因为非单例Bean默认不会初始化,而是使用时才会初始化),所以很简单咱们只需要手动。对于构造器注入出现缓存依赖,Spring 是无法解决的,因为当前 Bean 还未实例化,无法提前暴露对象,所以只能抛出异常。)​:存储Bean的ObjectFactory,用于创建早期的Bean实例。

qq_40463629的博客 2145

Spring — 循环依赖

读完这篇文章你将会收获到 Spring循环依赖可以分为哪两种 Spring如何解决 setter循环依赖 Spring为何是三级缓存 , 二级不行 ? Spring为啥不能解决构造器循环依赖 概述 循环依赖就是循环引用,两个或以上的 bean相互持有对方。比如说 beanA引用 beanB, beanB引用 beanC, beanC引用 beanA, 它们之间的引用关系构成一个环。 Spring 如何解决循环依赖 Spring中的循环依赖包括 构造器循环依赖 setter循环依赖 构造器的依赖 S

CoderLi 1422

Spring 是怎么解决循环依赖的

循环依赖,简单来说就是多个 Bean 之间相互依赖,形成一个闭环。例如,Bean A 依赖 Bean B,Bean B 又依赖 Bean C,而 Bean C 反过来又依赖 Bean A,这样就构成了循环依赖。在 Spring 容器创建 Bean 的过程中,如果遇到循环依赖,若不进行特殊处理,就会导致创建 Bean 的过程陷入死循环,无法成功初始化 Bean。​根据依赖注入方式的不同,循环依赖主要分为三种类型:​构造器循环依赖:通过构造函数进行依赖注入时产生的循环依赖。

qq_74181189的博客 1134

Spring Bean 循环依赖详解

时序图帮助理解Spring Bean 循环依赖

分享Java,Rust相关技术 1505

Spring如何循环依赖

一、什么是循环依赖 循环依赖指的是2个或2个以上的bean互相持有对方,从而形成闭环的依赖场景。例如A依赖B,B依赖C,而C又依赖A,这种情况就叫做循环依赖。 二、循环依赖的场景 1、构造器的循环依赖 2、setter循环循环依赖 三、Spring Bean的创建过程 源码见org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String) @Override public Object getBea

weixin_41951205的博客 1420

spring循环依赖

spring循环依赖主要有三种: 单例引用类型循环依赖(属性):允许 构造器的循环依赖:不允许 多例循环依赖:不允许 单例引用类型循环依赖(属性) package com.spring.bean; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Compo...

zhouyi_YYDS的博客 4988

Spring 循环依赖

先从一级缓存singletonObjects中去获取。(如果获取到就直接return)如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()),那就再从二级缓存earlySingletonObjects中获取。(如果获取到就直接return)如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。就从三级缓存singletonFactory.getObject()获取。

loushaoqi的博客 1256
上一篇: 插入数据时遇到主键重复问题怎么办?——insert into数据库技巧 (insert into主键重复数据库)
下一篇: RabbitMQ如何防止消息丢失及重复消费
涵冰...
博客等级 码龄4年 2438粉丝 243原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

涵冰...

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

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

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

打赏作者

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

抵扣说明:

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

余额充值