Spring MVC的父子容器

Spring MVC 之父子容器详解 Spring MVC 有父子容器,Spring Boot 整合了 Spring MVC,是否应该有父子容器,这也是面试中的一个常见问题,如果你回答是,肯可能就要回家等通知了,Spring Boot 是没有父子容器的概念的,这个在源码中可以看出来,Spring Boot 项目调用 SpringApplication#run 方法就可以启动项目,下面我们就来看下 SpringApplication#run 方法源码。欢迎提出建议及对错误的地方指出纠正。 阅读详情

1.背景

在使用Spring MVC时候大部分同学都会定义两个配置文件,一个是Spring的配置文件spring.xml,另一个是Spring MVC的配置文件spring-mvc.xml。

在这里给大家抛个问题,如果在spring.xml和spring-mvc.xml文件中同时定义一个相同id的单例bean会怎样呢?大家可以先思考一下再继续往下看。

我做了个实验,结论是:容器中会同时存在两个相同id 的bean,而且使用起来互不干扰。

这是为什么呢?学过Spring的同学肯定会质疑,众所周知id是bean的唯一标示,怎么可能同时存在两个相同id的bean呢?是不是我在胡扯呢?

原谅我在这和大家卖了个关子,其实大家说的都没错,因为这里涉及到Spring MVC父子容器的知识点。

这个知识点是:在使用Spring MVC过程中会存在Spring MVC 、Spring两个IOC容器,且Spring MVC是Spring的子容器。

那这个父子容器到底是什么呢?

为了保证我所说的权威性,而不是知识的二道贩子,我将从Spring 官方文档和源码两方面展开介绍。

2.Spring MVC父子容器

2.1 web.xml配置

还是先找程序入口,查看web.xml配置文件,找到Spring MVC相关配置。

<servlet>
        <servlet-name>spring-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
</servlet>

配置很简单,只是配置了一个类型为DispatcherServlet类型的Servlet,并设置了初始化参数。那DispatcherServlet是什么呢?

2.2 DispatcherServlet类介绍

查看API文档

从继承图看出最终继承自HttpServlet,其实就是一个普通的Servlet。那为什么这个Servlet就能完成Spring MVC一系列复杂的功能呢?继续往下看。

2.3 DispatcherServlet工作流程

 

DispatcherServlet工作流程如下:

(1) 所有请求先发到DispacherServlet 。

(2) DispacherServlet根据请求地址去查询相应的Controller,然后返回给DispacherServlet。

(3) DispacherServlet得到Controller后,让Controler处理相应的业务逻辑。

(4) Controler处理处理完后将结果返回给DispacherServlet。

(5) DispacherServlet把得到的结果用视图解析器解析后获得对应的页面。

(6) DispacherServlet跳转到解析后的页面。

在整个过程中DispatcherServlet承当了一个中心控制器的角色来处理各种请求。

2.4 DispatcherServlet上下文继承关系

 

上图来自Spring官网:

https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html

从图中可以看到DispatcherServlet里面有一个 Servlet WebApplicationContext,继承自 Root WebApplicationContext。

从上篇文章中我们知道WebApplicationContext其实就是一个IOC容器,root WebApplicationContext是Spring容器。

这说明DispatcherServlet中里创建了一个IOC容器并且这个容器继承了Spring 容器,也就是Spring的子容器。

而且官方文档中还有如下一段文字描述:

For many applications, having a single WebApplicationContext is simple and suffices. It is also possible to have a context hierarchy where one root WebApplicationContext is shared across multiple DispatcherServlet (or other Servlet) instances, each with its own child WebApplicationContext configuration. See Additional Capabilities of the ApplicationContext for more on the context hierarchy feature.

The root WebApplicationContext typically contains infrastructure beans, such as data repositories and business services that need to be shared across multiple Servlet instances.
Those beans are effectively inherited and can be overridden (that is, re-declared) in the Servlet-specific child WebApplicationContext, which typically contains beans local to the given Servlet.

结合图和上述文字我们可以得出以下信息:

  1. 应用中可以包含多个IOC容器。
  2. DispatcherServlet的创建的子容器主要包含Controller、view resolvers等和web相关的一些bean。
  3. 父容器root WebApplicationContex主要包含包含一些基础的bean,比如一些需要在多个servlet共享的dao、service等bean。
  4. 如果在子容器中找不到bean的时候可以去父容器查找bean。

看到这里也许大家心中也许就明白文章开头中我说的Spring MVC中的父子容器了,对那个问题也有了自己的判断和答案。

当然文章还没有结束,毕竟这还仅限于对官方文档的理解,为了进一步验证,我们拿出终极武器:

阅读源码!

2.5 DispatcherServlet源码分析

本小节我们分为Spring MVC容器的创建和bean的获取两部分进行分析。

2.5.1 Spring MVC容器的创建

前面分析到DispatcherServlet本质上还是一个Servlet ,既然是Servlet ,了解Servlet生命周期的同学都知道Web 容器装载Servlet第一步是执行init()函数,因此以DispatcherServlet 的init函数为突破口进行分析。

@Override
public final void init() throws ServletException {
   // 1.读取init parameters 等参数,其中就包括设置contextConfigLocation 
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
   //2.初始化servlet中使用的bean
   initServletBean();
}

在第1步读取init parameter的函数最终会调用setContextConfigLocation()设置配置文件路径。此处重点介绍initServletBean(),继续跟踪。

Override
protected final void initServletBean() throws ServletException {
      //初始化webApplicationContext
      this.webApplicationContext = initWebApplicationContext();
}
protected WebApplicationContext initWebApplicationContext() {
    //1.获得rootWebApplicationContext
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    //2.如果还没有webApplicatioinContext,创建webApplicationContext
    if (wac == null) {
    //创建webApplicationContext
        wac = createWebApplicationContext(rootContext);
    }
   return wac;
}

可以看到上面初始化webApplicationContext分为2步。

(1)获取父容器rootWebApplicationContext。

(2)创建子容器。

我们先看看rootWebApplicationContext是如何获取的。

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
   return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
   Object attr = sc.getAttribute(attrName);
   return (WebApplicationContext) attr;
}

从上面代码中我没看到是从ServletContext获取了名为“WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE”的webApplicationContext。

认真看过上篇文章的同学应该记得这个属性是在Spring初始化 容器initWebApplicationContext()函数中的第3步设置进去的,取得的值即Spring IOC容器。

继续看如何创建webApplicationContext。

protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
   return createWebApplicationContext((ApplicationContext) parent);
}
createWebApplicationContext(ApplicationContext parent) {
  //1.获取WebApplicationContext实现类,此处其实就是XmlWebApplicationContext
  Class<?> contextClass = getContextClass();
  //生成XmlWebApplicationContext实例
  ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
  //2.设置rootWebApplicationContext为父容器 
   wac.setParent(parent);
  //3.设置配置文件
   wac.setConfigLocation(getContextConfigLocation());
  //4.配置webApplicationContext.
   configureAndRefreshWebApplicationContext(wac);
   return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
   //开始处理bean
   wac.refresh();
}

看到这里同学们有没有是曾相识的感觉。是的,这段逻辑和上篇文章创建Spring IOC的逻辑类似。

唯一不同的是在第2步会把Spring容器设置为自己的父容器。至于新建容器中bean的注册、解析、实例化等流程和Spring IOC容器一样都是交给XmlWebApplicationContext类处理,还没有掌握的同学可以看上篇文章。

2.5.2 Spring MVC Bean的获取

Spring MVC bean的获取其实我们在上篇文章已经介绍过,这次再单拎出来介绍一下,加深记忆。

protected <T> T doGetBean(
    // 获取父BeanFactory
    BeanFactory parentBeanFactory = getParentBeanFactory();
    //如果父容器不为空,且本容器没有注册此bean就去父容器中获取bean
    if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
         // 如果父容器有该bean,则调用父beanFactory的方法获得该bean
         return (T) parentBeanFactory.getBean(nameToLookup,args);
    }
    //如果子容器注册了bean,执行一系列实例化bean操作后返回bean.
    //此处省略实例化过程
    .....
    return (T) bean;
}

上面代码就可以对应官方文档中“如果子容器中找不到bean,就去父容器找”的解释了。

3.小结

看完上面的介绍,相信大家对Spring MVC父子容器的概念都有所了解,现在我们分析文章开头的问题。

如果spring.xml和spring-mvc.xml定义了相同id的bean会怎样?假设id=test。

1.首先Spring 初始化,Spring IOC 容器中生成一个id为test bean实例。

2.Spring MVC开始初始化,生成一个id为test bean实例。

此时,两个容器分别有一个相同id的bean。那用起来会不会混淆?

答案是不会。

当你在Spring MVC业务逻辑中使用该bean时,Spring MVC会直接返回自己容器的bean。

当你在Spring业务逻辑中使用该bean时,因为子容器的bean对父亲是不可见的,因此会直接返回Spring容器中的bean。

虽然上面的写法不会造成问题。但是在实际使用过程中,建议大家都把bean定义都写在spring.xml文件中。

因为使用单例bean的初衷是在IOC容器中只存在一个实例,如果两个配置文件都定义,会产生两个相同的实例,造成资源的浪费,也容易在某些场景下引发缺陷。

4.尾声

现在大家基本都不使用在xml文件中定义bean的形式,而是用注解来定义bean,然后在xml文件中定义扫描包。如下:

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

那如果在spring.xml和spring-mvc.xml配置了重复的包会怎样呢?

如果本文看明白的同学此时已经知道了答案。

答案是会在两个父子IOC容器中生成大量的相同bean,这就会造成内存资源的浪费。

也许有同学想到,那只在spring.xml中设置扫描包不就能避免这种问题发生了吗,答案是这样吗?

大家可以试试,这样会有什么问题。如果不行,那是为什么呢?

欲知分晓,敬请期待下篇分解!

https://blog.youkuaiyun.com/luojinbai/article/details/85877956#3.%20%40ComponentScan%E8%BF%87%E6%BB%A4%E8%A7%84%E5%88%99%E8%AF%B4%E6%98%8E

https://www.cnblogs.com/163yun/p/9876172.html

https://www.cnblogs.com/hafiz/p/5875740.html

https://www.cnblogs.com/hafiz/p/5875770.html

https://www.jianshu.com/p/bd36c023ddf0

SpringCloud 父子容器

https://blog.youkuaiyun.com/woshilijiuyi/article/details/86663049

 

【Spring】Spring 父子容器 创建spring容器的时候,可以给当前容器指定一个父容器。BeanFactory的方式//创建父容器parentFactory DefaultListableBeanFactory parentFactory = new DefaultListableBeanFactory();//创建一个子容器childFactory DefaultListableBeanFactory childFactory = new DefaultListableBeanFactory(); 阅读详情

相关推荐

Ansys Lumerical 启动时出现 Error: Could not connect to Ansys license server specified at 1055@ 的一种解决办法

考虑到 2020 版的时候是直接在全局变量里指定具体地址,但是 2024 版本是写成 1055@localhost ,并且之后在站里搜相关 Ansys 的问题都指向 1055 端口被占用,虽然问题不一样,但是本着死马当活马医,就按照。我最近在跑超透镜结构,听说2024版本现在有GPU加速可以用,就把Lumerical从2020升到2024版本,结果发现一开头的破解办法完全不一样了。一开始安装的时候以为2020没有删干净,导致2024版的安装一直锁着目标地址,解决办法参考。的办法来操作,在关闭了。

qq_51404184的博客 5752

SpringMVC源码(三):Spring整合SpringMVC以及父子容器启动流程分析

Spring整合SpringMVC Spring整合SpringMVC的方式是父子容器形式 父容器Spring管理Service、Dao层的Bean 子容器SpringMVC管理Controller的Bean 子容器可以访问父容器的Bean,父容器无法访问子容器的Bean 而SpringBoot是SpringMVC零Xml配置基础上出现的,那SpringMVC的零Xml配置是如何实现的? 就是把web.xml中负责父容器ContextLoaderListener和子容器DispatcherServlet通过

Men-DD 1141

YonSuite如何新增用户并分配角色角色授权图文教程

用友YonSuite产品基础教学

ct341688的博客 617

Spring 系列(二):Spring MVC的父子容器

1.背景 在使用Spring MVC时候大部分同学都会定义两个配置文件,一个是Spring的配置文件spring.xml,另一个是Spring MVC的配置文件spring-mvc.xml。 在这里给大家抛个问题,如果在spring.xml和spring-mvc.xml文件中同时定义一个相同id的单例bean会怎样呢?大家可以先思考一下再继续往下看。 我做了个实验,结论是:容器中会同时存在两个相同...

weixin_33826268的博客 294

【滤波跟踪】多雷达协同下的多目标跟踪:航迹管理优化与卡尔曼滤波融合策略

本文深入探讨了多雷达协同下的多目标跟踪技术,聚焦于航迹管理优化与卡尔曼滤波融合策略两大核心。通过分析分布式融合架构、航迹关联算法(如匈牙利算法)以及应对非线性与机动目标的扩展卡尔曼滤波(EKF)与无迹卡尔曼滤波(UKF)等方法,旨在解决多源信息融合难题,提升复杂环境下目标跟踪的精度与鲁棒性。

weixin_29214199的博客 280

silvaco——GaN HEMT探索

上图为输出特性曲线 ,分别是在栅压为1v、3v、5v的情况下,可以看出在1v时器件处于截止状态,只有非常小近似于0的电流通过,随着栅压逐渐增大,沟道开启,产生漏极电流。上图为栅极漏电流曲线和转移特性曲线以及其提取的参数,可以看到阈值电压为=1.30249V,当栅压为0v时,Id=8.01032e-006,当栅压为5V时,Ig=3.11627e-006。albrct.n,低场迁移率模型,bn.albrct,an.albrct是albrct模型中的参数,可以由用户自己设定。lat.temp,晶格自热模型;

weixin_61913839的博客 7616

Spring MVC 父子容器是什么?这篇文章讲清楚了

Spring MVC 父子容器是初学 Spring MVC 时最先接触到 Spring 知识点之一,还记得我刚工作那会,项目基础架构是其他同事搭建的,其中就用到了 Spring MVC 中的父子容器,还把 Spring MVC 中的不同层拆成了不同的 maven 模块。这里暂不讨论这种模块拆分方式的优劣,Spring 为什么设计出具有层次结构的容器呢?Web 环境中什么场景会用到这种具有层次结构的容器?

大鹏的博客 8063

2024“华为杯”中国研究生数学建模竞赛(A题)深度剖析_数学建模完整过程+详细思路+代码全解析

目标是优化风力涡轮机的功率调度,使其在给定的实时风速数据下,最小化涡轮机关键组件(如塔架和主轴)的疲劳损伤。问题的挑战在于,考虑通信延迟和测量噪声的同时,要确保功率输出与风速变化的响应快速且准确。

2401_82549447的博客 6628

Spring MVC父子容器背后的顺序关系

Spring的父子容器主要出现在Spring MVC时代,随着Spring Boot这位行业颠覆者的问世,以其简单的配置,快速搭建Web项目的能力,迅速取代Spring MVC的江湖地位。

static_coder的博客 907

【STM32学习】(29)STM32实现595驱动三个数码管(标准库和HAL库实现)

我选用的单片机型号为:STM32F103系列 74LS595是一个串转并行输出的芯片,它能为单片机节省很多的IO口,应用场景广泛。 现要求三个数码管动态显示,常规设计都是并行实现,需要8个IO数据口,然后还需要3个IO片选数码管接口,这样大大占用了IO口,导致连接的外设变少。因此我们引入74LS595芯片。电路设计如下: 只需要三个共阴极数码管与三个595芯片级联即可完成,通过RCLK,SCK,SER三个IO即可完成对数码管的动态控制并显示。电路焊接的实物图如下: 要求完成功能:实现数.

XiaoCaiDaYong的博客 8264

FileZilla Server 1.2.0 TLS证书过期问题

FileZilla Server TLS证书过期后重新生成

Wigle的备忘录 7526

说下对 Spring MVC 的理解? Spring MVC 具体的工作原理? SpringMVC 父子容器是什么知道吗?

Spring MVC 是基于 MVC 模式的 Web 框架,通过统一分发请求,借助找到并执行 Controller,再通过渲染视图;父子容器则是为了分离 Web 层与业务层 Bean 的管理,实现职责解耦与作用域隔离,让架构更清晰、可维护。理解这些,就能掌握 Spring MVC 的核心流程和设计思想,应对开发中的请求处理、分层解耦等问题。

weixin_43521001的博客 964

2025_NIPS_Grounded Reinforcement Learning for Visual Reasoning

尽管基于思维链的强化学习已在数学、编程等任务中显著提升语言模型性能,但视觉推理面临额外复杂性——需模型引导视觉注意力、解读感知输入,并将抽象推理锚定到空间证据上。本文提出ViGoRL(视觉接地强化学习),这是一种通过强化学习训练的视觉语言模型,能将每个推理步骤明确锚定到特定视觉坐标。受人类视觉决策启发,ViGoRL学会生成空间接地的推理轨迹,在每个步骤引导视觉注意力聚焦任务相关区域。当需要细粒度探索时,我们提出的多轮强化学习框架允许模型随着推理展开动态缩放至预测坐标。

c_cpp_csharp的专栏 59

探索Spring MVC SPI扩展机制:父子容器刷新原理

SPI(Service Provider Interface)扩展机制是一种服务发现机制,广泛用于 Java 编程中,允许框架或应用程序在运行时发现并加载外部提供的服务实现。SPI 主要通过接口定义服务,并通过配置文件和类加载器机制动态加载服务提供者(即具体的实现类)。在 Java 中,SPI 是基于反射和接口的一种插件式架构,使得框架能够在运行时根据需求替换或扩展服务实现。Spring MVC 利用 SPI 扩展机制来加载和管理各种组件,例如视图解析器、数据绑定、处理器映射等。

qq_26733517的博客 896

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Spring MVC 父子容器深度解析:原理、实战与优化

文章摘要:Spring MVC的父子容器架构通过解耦Web层与业务层实现职责分离,父容器(Root)管理Service、DAO等业务组件,子容器(Web)处理Controller等Web组件。采用分层设计(XML/JavaConfig配置),子容器可访问父容器Bean而反向不可行,事务管理器等核心组件必须置于父容器。该架构适用于中大型单体应用,支持多Web模块共享业务逻辑,但在微服务场景中价值降低。优化建议包括包路径隔离、@Primary注解解决冲突,以及通过WebApplicationInitialize

持续输出Java相关知识 1221

Statistics and Machine Learning Toolbox

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

spring mvc 父子容器AOP-建言者(切面,事务)的继承

spring 是含有父子容器的机制的,特别是在使用springmvc的情况下,父子容器是非常容易出现的,也是大家常用的。那么父子容器之间的关系到底是怎么样的呢。 之前听说和看别的文章说,springmvc中父容器事务不起效,不要使用父子容器都加载配置信息等,这些会在本文中揭开疑惑。 所以提出了几个疑问,父子容器之间的关系是怎样生成的,父子容器之间的关系是什么样的,父子容器方面有什么特殊实现呢(特

songyang19871115的博客 3414

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

Spring系列:父子容器详解

又一次被面试官带到坑里面了。 面试官:springmvc用过么? 我:用过啊,经常用呢 面试官:springmvc中为什么需要用父子容器? 我:嗯。。。没听明白你说的什么。 面试官:就是controller层交给一个spring容器加载,其他的service和dao层交给另外一个spring容器加载,web.xml中有这块配置,这两个容器组成了父子容器的关系。 我:哦,原来是这块啊,我想起来了,我看大家都这么用,所以我也这么用 面试官:有没有考虑过为什么? 我:我在网上看大家都这么用,所以我也这么用了,具体

qq_43842093的博客 6191

深入底层,spring mvc父子容器初始化过程解析

以下是对各个组件主要职责的总结:根据RootConfig创建了一个上下文:RootApplicationContext向ServletContext注册了ContextLoaderListener,传入了RootApplicationContext根据ServletConfig创建了一个上下文:ServletApplicationContext向ServletContext注册了DispatcherServlet,传入了ServletApplicationContext。

学无止境! 2375

Spring系列第24篇:父子容器详解

又一次被面试官带到坑里面了。面试官:springmvc用过么?我:用过啊,经常用呢面试官:springmvc中为什么需要用父子容器?我:嗯。。。没听明白你说的什么。面试官:就是contr...

路人甲Java 9万+

Spring MVC学习(2)—Spring MVC中容器的层次结构以及父子容器的概念

基于最新Spring 5.x,详细介绍了Spring MVC中容器的层次结构以及父子容器的概念。

终生学习践行者 6847

spring容器和spring mvc容器的父子容器关系

Spring和SpringMVC作为Bean管理容器和MVC层的默认框架,已被众多WEB应用采用,而实际使用时,由于有了强大的注解功能,很多基于XML的配置方式已经被替代,但是在实际项目中,同时配置Spring和SpringMVC时会出现一些奇怪的异常,比如Bean被多次加载,多次实例化,或者依赖注入时,Bean不能被自动注入,但是明明你已经将该Bean注册了的。找原因还是要看问题的根源,我们从容...

xuanwugang的博客 609

3.[springMvc]spring mvc 父子容器

spring mvc 父子容器spring MVC的实现方式spring 父子容器父容器创建过程initWebApplicationContextcreateWebApplicationContextconfigureAndRefreshWebApplicationContext示例子容器创建过程HttpServletBean.initFrameworkServlet.initServletBeaninitWebApplicationContextcreateWebApplicationContextonR

JAVA技术爱好者 435

boot spring 没有父子容器_Spring 系列(二):Spring MVC的父子容器

1.背景在使用Spring MVC时候大部分同学都会定义两个配置文件,一个是Spring的配置文件spring.xml,另一个是Spring MVC的配置文件spring-mvc.xml。在这里给大家抛个问题,如果在spring.xml和spring-mvc.xml文件中同时定义一个相同id的单例bean会怎样呢?大家可以先思考一下再继续往下看。我做了个实验,结论是:容器中会同时存在两个相同id ...

weixin_35308269的博客 246
上一篇: JDK和Spring中SPI的实现原理和区别
下一篇: DispatcherServlet与ContextLoaderListener的对比
低调的杉菜
博客等级 码龄15年 12粉丝 34原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值