spring、springbean的生命周期相关知识点

什么是spring

spring是一个轻量级的控制反转和面向横切面编程的框架。

SpringBean的注入方式由哪几种?参考

  • 构造器注入
  • set注入
  • 静态工厂注入
  • 实例工厂注入

springbean的生命周期

什么是springBean的生命周期?

Bean从在Spring中创建开始,到Bean被销毁结束,这一过程称之为Bean的生命周期。
soringbean的生命周期可以分为五个部分,对象的创建依赖注入初始化使用销毁

1:对象的创建

Spring在启动的时候需要「扫描」在XML/注解/JavaConfig 中需要被Spring管理的Bean信息
随后,会将这些信息封装成BeanDefinition,最后会把这些信息放到一个beanDefinitionMap中。这个Map的key是beanName,value则是BeanDefinition对象,BeanDefinition对象里面存放的是bean的描述信息,可能来自于xml文件也可能来自于注解。
在这里插入图片描述

到这里其实就是把定义的元数据加载起来,如果是使用的xml文件配置的bean对象,那么这个时候对象的属性还没有被赋值,目前真实对象还没实例化。

创建对象包括几个步骤,实例化初始化,实例化只是开辟一块空间属性都是默认值,初始化包括属性填充和调用初始化方法两个步骤。

有了这个beanDefinition对象之后,还需要对beanDefinition里面的属性进行填充,比如说我们的配置文件里面写的是${datasource.username},这里的值是什么时候从配置文件里面取出来的呢?
这个时候beanDefiniton里面是没有具体的值的,需要bean工厂的后置处理器进行处理之后才会有值。
在这里插入图片描述
用来处理这个\${datasource.username}的工具类就是PlaceholderConfigurerSupport这个类。这个类对BeanDefinition进行处理之后的BeanDifinition里面的属性值就不再是原来的${datasource.username}了,而是从配置文件中取出来的实际的值。

在这里插入图片描述
再比如:ConfigurationClassParser这个实现类,能够处理很多的注解,
在这里插入图片描述

构造器推断

首先实例化就是利用类的构造器创建对象的过程,那么在实例化之前一定要知道用哪一个构造器,这就是构造器推断,这个步骤是由AutowiredAnnotationBeanPostProcessor找出哪些构造器上有@Autowired注解,寻找的过程是
1:对beanClass中所有的构造器进行遍历,找到带有@Autowired注解的构造器加入到候选构造器集合中。
2:对候选构造器集合中的内容进行遍历,找到@Autowired注解中require属性是否是true(默认是true),如果是,则不能再有其他@Autowired注解的构造器,有的话就报异常。如果不是,那就可以继续添加带有@Autowired注解并且require属性为false的构造器。
3:如果候选构造器集合不为空,并且beanClass中还有空构造器。那么就将该空构造器加入到构造器集合中。如果构造器集合为空,且beanClass中只有一个有参的构造器那么就会将此构造器加入到构造器集合中。如果这种情况下还没有有参构造只有无参构造那就会使用无参构造,如果有多个构造器也会使用无参构造。

对象的实例化

ConstructorResolver根据候选构造器集合中构造器的优先级对beanClass进行实例化,这个过程主要有三个问题

1:在创建对象前后执行方法

创建前后的增强

  • postProcessBeforeInstantiation
    • 这里返回的对象若不为 null 会替换掉原本的 bean,并且仅会走 postProcessAfterInitialization 流程
  • postProcessAfterInstantiation
    • 这里如果返回 false 会跳过依赖注入阶段

2:构造器的优先级怎样确定

首先public修饰的构造器优先权高于private修饰的构造器。
修饰相同的情况下构造器参数多的优先权更高。

3:如果选定的构造器中需要的bean不在Spring容器中怎么办?

首先对根据优先级排序好的bean进行遍历尝试调用,如果正常调用就没问题,并直接退出遍历,如果出现了参数不在spring容器中则遍历下一个bean,如果没有一个可以调用的就会抛出异常。

2: 依赖注入(对属性进行填充)

依赖注入涉及到以下几个问题

依赖注入前的增强

  • postProcessProperties
    • 如 @Autowired、@Value、@Resource

1:BeanDefination:找到被@Autowired、@Value,@Resource,@Inject等注解注解的属性

这个过程由下面两个处理器来进行处理

1、AutowiredAnnotationBeanPostProcessor:处理@Autowird,@Value,@Inject注解

2、CommonAnnotationBeanPostProcessor:处理@PostConstruct,@PreDestroy,@Resource注解

处理的过程是首先遍历bean中所有的属性和方法,判断是都有上面相关的注解,有的话就把他们都封装到一个叫做InjectionMetadata的对象中,然后对这个对象中的成员进行判断是否被注解了两次,比如被@Autowird、@Resource同时注解,没有被解析就会被放到已经检查的元素集合中,检查过了就不会放入,这样就避免了重复解析。

真正的依赖注入

使用beanName为key从缓存中取出InjectionMetadata,然后对它进行遍历,根据类型从spring容器中获取对应的对象,然后利用反射注入到bean的属性中。
在这里插入图片描述

3:初始化

初始化的过程涉及初始化前增强,初始化方法,初始化后的增强

初始化前的增强:postProcessBeforeInitialization

  • 这里返回的对象会替换掉原本的 bean
  • 如 @PostConstruct、@ConfigurationProperties
  • 处理一系列Aware接口的回调方法

执行初始化的方法

初始化后的增强:postProcessAfterInitialization

  • 这里返回的对象会替换掉原本的 bean
  • 如代理增强
4:使用
5:卸载

执行 @PreDestory的方法

如果 Bean 实现了 DisposableBean 这个接口,会调用那个其实现的 destroy()方法;

最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会自动调用其配置的
销毁方法。

总结

再创建一个bean对象之前并不是一上来就取读取配置文件,而是首先把容器创建出来,不然我读取文件之后得到的BeanDefinition放在哪呢?所以首先会调用AbstractApplicationContext的相关的refresh方法。设置BeanFactory的相关的属性值。
在这里插入图片描述
然后执行相关的BeanFactoryPostProcessor方法。

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
				// Invoke factory processors registered as beans in the context.
				//执行相关的增强方法
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				//注册bean的处理器
				registerBeanPostProcessors(beanFactory);
				beanPostProcess.end();

				// Initialize message source for this context.
				//处理国际化的
				initMessageSource();

				// Initialize event multicaster for this context.
				//初始化时间监听多路广播器
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				//留给子类来初始化其他的bean 
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
				contextRefresh.end();
			}
		}
	}

更全面的讲解
参考视频

spring如何解决循环依赖问题?如果只有一级缓存能不能解决循环依赖?只有二级缓存能不能解决循环依赖问题?

在这里插入图片描述

spring使用三级缓存来解决循环依赖问题,一级缓存存放成熟的bean,二级缓存存放早期的bean,就是还没有进行依赖注入的bean。如果只有一级缓存那么成品对象和半成品对象都会放到同一级缓存中,就无法区分一个对象是成熟的还是早期的。如果只有二级缓存,在没有动态代理的情况下是可以解决循环依赖的问题的,但是如果添加了aop之后,有了代理对象的情况下二级缓存无法解决循环依赖问题。

为什么添加了AOP之后就需要三级缓存来解决循环依赖?

三级缓存添加了一个操作:getEarlyBeanReference方法。
在创建代理对象的时候还需要创建原始对象,在创建完原始对象之后还需要创建代理对象,也就是说一个beanName会存在两个对象,但是在整个容器中一个beanName只对应一个对象,在生成代理对象的时候就要把原来的对象覆盖掉。
那程序是如何知道什么时候要进行代理对象的创建呢?
这就需要一个接口,当我这个对象第一次暴露使用的时候就要通过这个接口判断这个对象是否需要创建代理对象。这个接口就是上面所说的getEarlyBeanReference,这个方法判断如果需要创建代理对象就创建返回代理之后的对象,如果不需要创建代理对象就直接返回原始的对象。这就是三级缓存的意义,在三级缓存中判断是原始对象还是代理对象。

在这里插入图片描述

Spring在什么情况下无法解决循环依赖问题?

  • 在多例的bean存在相互依赖的时候。解决方式:在需要进行依赖注入的属性上添加@Lazy懒加载。
  • 在进行构造器注入的情况下的循环依赖问题。解决方式:在构造器的参数上面添加注解@Lazy
  • 单例的代理bean在存在循环依赖的时候无法解决。解决方式:适合用@Lazy
  • 设置了@DependsOn 的 Bean 的情况,不能解决循环依赖问题

BeanFactory和FactoryBean有什么区别?

  • BeanFactory

BeanFactory,以Factory结尾,表示它是一个工厂类(接口), 它负责生产和管理bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,其中XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。XmlBeanFactory类将持有此XML配置元数据,并用它来构建一个完全可配置的系统或应用。

  • FactoryBean

一般情况下,Spring通过反射机制利用的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。

FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明改为FactoryBean的形式,以Bean结尾,表示它是一个Bean,不同于普通Bean的是:它是实现了FactoryBean接口的Bean,根据该Bean的ID从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个&符号来获取。

在这里插入图片描述

Spring中用到的设计模式有哪些?

单例模式:bean默认都是单例的。
工厂模式:beanFactory
策略模式:XmlBeanDefinitionReader、PropertiesBeanDefinitionReader
模板方法设计模式:postProcessorBeanFactory、onRefresh、initPropertyValue
观察者模式:listener、event、multicast
适配器模式:Adapter
装饰着模式:BeanWrapper
责任链模式:使用aop的时候会先生成一个拦截器链
代理模式:动态代理
委托者模式:delegate
… … …

源码分析

在类DefaultListableBeanFactory中可以看到这个方法

@Override
	public void preInstantiateSingletons() throws BeansException {
		if (logger.isTraceEnabled()) {
			logger.trace("Pre-instantiating singletons in " + this);
		}

		// Iterate over a copy to allow for init methods which in turn register new bean definitions.
		// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
		//根据所有的beanDefinition的名字创建一个集合
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		//触发所有非延迟加载的单例bean的初始化
		for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			//条件判断,抽象单例、非懒加载
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				//判断是都实现了FactoryBean接口,
				if (isFactoryBean(beanName)) {
					//如果实现了FactoryBean接口就要用&+beanName来获取对象
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) {
					//这里的getBean就是实例化的第一步
						getBean(beanName);
					}
				}
				else {
					//如果没有实现FactoryBean接口,只是普通的bean,直接通过beanName获取即可。
					//这里的getBean就是实例化的第一步
					getBean(beanName);
				}
			}
		}

		// Trigger post-initialization callback for all applicable beans...
		for (String beanName : beanNames) {
			Object singletonInstance = getSingleton(beanName);
			if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {
				StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize")
						.tag("beanName", beanName);
				smartSingleton.afterSingletonsInstantiated();
				smartInitialize.end();
			}
		}
	}

点进入这个getBean可以看到

@Override
	public Object getBean(String name) throws BeansException {
		return doGetBean(name, null, null, false);
	}

进入doGetBean,截取部分代码进行查看,这里就可以看到循环依赖相关的解决方法

@SuppressWarnings("unchecked")
	protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {

		String beanName = transformedBeanName(name);
		Object beanInstance;

		// Eagerly check singleton cache for manually registered singletons.
		//提前检查单例缓冲池中是否已经有了手动注册的单例对象,跟循环依赖有关联
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			if (logger.isTraceEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
			beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}

进去这个getSingleton

public Object getSingleton(String beanName) {
		return getSingleton(beanName, true);
	}

继续点进去这个getSingleton

@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// Quick check for existing instance without full singleton lock
		//从单例缓存中获取单例对象,这里就是从一级缓存里面取
		Object singletonObject = this.singletonObjects.get(beanName);
		//如果一级缓存你里面是空的或者处于正在被创建的过程中
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			//如果一级缓存中没有取到就会从二级缓存里面取
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
					// Consistent creation of early reference within full singleton lock
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								singletonObject = singletonFactory.getObject();
								this.earlySingletonObjects.put(beanName, singletonObject);
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

如果在缓存中都没有发现这个对象,那么后面会调用

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
		synchronized (this.singletonObjects) {
			//从一级缓存里面取对象
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				if (this.singletonsCurrentlyInDestruction) {
					throw new BeanCreationNotAllowedException(beanName,
							"Singleton bean creation not allowed while singletons of this factory are in destruction " +
							"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
				}
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<>();
				}
				try {
					//从单例工厂中获取bean,这里getObject调用的就是参数中的lambda表达式
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
					// Has the singleton object implicitly appeared in the meantime ->
					// if yes, proceed with it since the exception indicates that state.
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						throw ex;
					}
				}
				catch (BeanCreationException ex) {
					if (recordSuppressedExceptions) {
						for (Exception suppressedException : this.suppressedExceptions) {
							ex.addRelatedCause(suppressedException);
						}
					}
					throw ex;
				}
				finally {
					if (recordSuppressedExceptions) {
						this.suppressedExceptions = null;
					}
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

代码中的方法singletonObject = singletonFactory.getObject();会执行createBean

// Create bean instance.
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
					beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}

这个createBean会执行doCreateBeandoCreateBean又会执行createBeanInstance

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// Make sure bean class is actually resolved at this point.
		Class<?> beanClass = resolveBeanClass(mbd, beanName);

		if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
		}

		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}

		if (mbd.getFactoryMethodName() != null) {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}

		// Shortcut when re-creating the same bean...
		boolean resolved = false;
		boolean autowireNecessary = false;
		if (args == null) {
			synchronized (mbd.constructorArgumentLock) {
				if (mbd.resolvedConstructorOrFactoryMethod != null) {
					resolved = true;
					autowireNecessary = mbd.constructorArgumentsResolved;
				}
			}
		}
		if (resolved) {
			if (autowireNecessary) {
				return autowireConstructor(beanName, mbd, null, null);
			}
			else {
				return instantiateBean(beanName, mbd);
			}
		}

		// Candidate constructors for autowiring?
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			return autowireConstructor(beanName, mbd, ctors, args);
		}

		// Preferred constructors for default construction?
		ctors = mbd.getPreferredConstructors();
		if (ctors != null) {
			return autowireConstructor(beanName, mbd, ctors, null);
		}

		// No special handling: simply use no-arg constructor.
		return instantiateBean(beanName, mbd);
	}

上面的方法执行之后,我们就可以有了一个实例化之后的bean对象
下面我们继续回到doCreateBean方法的后面,这里面可以看到下面的代码

if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			//为了避免后期的循环依赖,可以将bean在初始化完成前将创建实例的ObjectFactory加入工厂
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

点进去这个方法

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		//把实例化的对象放入三级缓存,key存放的是beanName,value存放的是lambda表达式
		synchronized (this.singletonObjects) {
			if (!this.singletonObjects.containsKey(beanName)) {
				//放入到三级缓存中
				this.singletonFactories.put(beanName, singletonFactory);
				//将beanName从二级缓存中移除
				this.earlySingletonObjects.remove(beanName);
				//注册到三级缓存中
				this.registeredSingletons.add(beanName);
			}
		}
	}

这个方法执行完之后我们继续进入前面的doCreateBean方法的后面,可以发现这里执行了populateBean方法

// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
			}
		}

在populate方法里面可以看到这个部分代码
就是对属性值进行填充,这个填充的过程中如果没有在缓存中找不到相应的值就会实例化一个bean对象,然后继续执行上面的过程。这个时候即使出现循环依赖,在初始化这个对象的时候它所依赖的对象已经放在了缓存中,这就解决了循环依赖的问题。这个时候在把这个实例化的对象赋值给原来的bean的属性。

if (pvs != null) {
			applyPropertyValues(beanName, mbd, bw, pvs);
		}

SpringBean的生命周期中有哪些扩展点

推荐文章

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

北海冥鱼未眠

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

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

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

打赏作者

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

抵扣说明:

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

余额充值