ApplicationContext和Bean的初始化和销毁

本文详细介绍了Spring框架中Bean的生命周期,包括Bean的初始化和销毁过程。重点讲解了ApplicationContext如何管理和控制Bean的整个生命周期,从Bean的创建、初始化、使用到最终的销毁。

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

本节来学习ApplicationContext和Bean的初始化和销毁

首先看一下ApplicationContext容器的初始化和销毁关闭过程。

ApplicationContext启动的过程是在AbstractApplicationContext实现的。在使用应用上下文时需要做一些准备工作,这些准备工作在prepareBeanFactory方法中实现。这个方法为容器配置了ClassLoader、PropertyEditor和BeanPostProcessor等。

	protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		// Tell the internal bean factory to use the context's class loader etc.
		beanFactory.setBeanClassLoader(getClassLoader());
		beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
		beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

		// Configure the bean factory with context callbacks.
		beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
		beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
		beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
		beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
		beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
		beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
		beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

		// BeanFactory interface not registered as resolvable type in a plain factory.
		// MessageSource registered (and found for autowiring) as a bean.
		beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
		beanFactory.registerResolvableDependency(ResourceLoader.class, this);
		beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
		beanFactory.registerResolvableDependency(ApplicationContext.class, this);

		// Register early post-processor for detecting inner beans as ApplicationListeners.
		beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

		// Detect a LoadTimeWeaver and prepare for weaving, if found.
		if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
			beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
			// Set a temporary ClassLoader for type matching.
			beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
		}

		// Register default environment beans.
		if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
			beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
		}
		if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
			beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
		}
		if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
			beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
		}
	}

在关闭容器时,也要完成一系统的工作,这些工作在doClose()方法中完成。

在这个方法中,先发出容器关闭的信号,然后将Bean逐个关闭,最后关闭容器自身。

	protected void doClose() {
		if (this.active.get() && this.closed.compareAndSet(false, true)) {
			if (logger.isInfoEnabled()) {
				logger.info("Closing " + this);
			}

			LiveBeansView.unregisterApplicationContext(this);

			try {
				// Publish shutdown event.
				publishEvent(new ContextClosedEvent(this));
			}
			catch (Throwable ex) {
				logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
			}

			// Stop all Lifecycle beans, to avoid delays during individual destruction.
			if (this.lifecycleProcessor != null) {
				try {
					this.lifecycleProcessor.onClose();
				}
				catch (Throwable ex) {
					logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
				}
			}

			// Destroy all cached singletons in the context's BeanFactory.
			destroyBeans();

			// Close the state of this context itself.
			closeBeanFactory();

			// Let subclasses do some final clean-up if they wish...
			onClose();

			this.active.set(false);
		}
	}

容器的实现是通过IOC管理Bean的生命周期来实现的。Spring IOC容器在对Bean生命周期进行管理时提供了Bean生命周期各个时间点的回调。

简单介绍一下IOC容器中Bean的生命周期

  • Bean实例的创建
  • 为Bean的实例设置属性
  • 调用Bean的初始化方法
  • 应用可以通过IOC容器使用Bean
  • 当容器关闭时,调用Bean的销毁方法

Bean的初始化方法是在AbstractAutowireCapableBeanFactory的initializeBean方法中实现的:

	protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
		if (System.getSecurityManager() != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				invokeAwareMethods(beanName, bean);
				return null;
			}, getAccessControlContext());
		}
		else {
			invokeAwareMethods(beanName, bean);
		}

		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		try {
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null),
					beanName, "Invocation of init method failed", ex);
		}
		if (mbd == null || !mbd.isSynthetic()) {
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}

		return wrappedBean;
	}
	private void invokeAwareMethods(final String beanName, final Object bean) {
		if (bean instanceof Aware) {
			if (bean instanceof BeanNameAware) {
				((BeanNameAware) bean).setBeanName(beanName);
			}
			if (bean instanceof BeanClassLoaderAware) {
				ClassLoader bcl = getBeanClassLoader();
				if (bcl != null) {
					((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
				}
			}
			if (bean instanceof BeanFactoryAware) {
				((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
			}
		}
	}

在调用Bean的初始化方法之前,会调用一系列的aware接口实现,把相关的BeanName、BeanClassLoader、BeanFactory注入到Bean中。

接着会看到进行Bean的后置处理,进入applyBeanPostProcessorsBeforeInitialization方法

BeanPostProcessor是Bean的后置处理器,是一个监听器,可以监听容器触发的事件。将它向IOC容器注册后,容器中管理的Bean具备了接收IOC容器事件回调的能力。

这个BeanPostProcessor是一个接口,有两个接口方法,一个是postProcessBeforeInitialization,在Bean的初始化前提供回调入口;一个是postProcessAfterInitialization,在Bean的初始化后提供回调入口,这两个回调的触发都是和容器管理Bean的生命周期相关的。两个方法的参数是一样的,分别是Bean的实例化对象和bean的名字。


	@Override
	public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
			Object current = beanProcessor.postProcessBeforeInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}

这里会依次调用BeanPostProcessor的postProcessBeforeInitialization方法。


接着会看到对invokeInitMethods的调用,这时还会看到启动afterPropertiesSet的过程,当然,这需要Bean实现InitializingBean的接口,对应的初始化处理可以在afterPropertiesSet方法实现。

	protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
			throws Throwable {

		boolean isInitializingBean = (bean instanceof InitializingBean);
		if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
			if (logger.isDebugEnabled()) {
				logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
			}
			if (System.getSecurityManager() != null) {
				try {
					AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
						((InitializingBean) bean).afterPropertiesSet();
						return null;
					}, getAccessControlContext());
				}
				catch (PrivilegedActionException pae) {
					throw pae.getException();
				}
			}
			else {
				((InitializingBean) bean).afterPropertiesSet();
			}
		}

		if (mbd != null && bean.getClass() != NullBean.class) {
			String initMethodName = mbd.getInitMethodName();
			if (StringUtils.hasLength(initMethodName) &&
					!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
					!mbd.isExternallyManagedInitMethod(initMethodName)) {
				invokeCustomInitMethod(beanName, bean, mbd);
			}
		}
	}

最后还会判断Bean是否配置initMethod,如果有,那么通过invokeCustomInitMethod方法调用,最终完成Bean的初始化。

	protected void invokeCustomInitMethod(String beanName, final Object bean, RootBeanDefinition mbd)
			throws Throwable {

		String initMethodName = mbd.getInitMethodName();
		Assert.state(initMethodName != null, "No init method set");
		final Method initMethod = (mbd.isNonPublicAccessAllowed() ?
				BeanUtils.findMethod(bean.getClass(), initMethodName) :
				ClassUtils.getMethodIfAvailable(bean.getClass(), initMethodName));

		if (initMethod == null) {
			if (mbd.isEnforceInitMethod()) {
				throw new BeanDefinitionValidationException("Couldn't find an init method named '" +
						initMethodName + "' on bean with name '" + beanName + "'");
			}
			else {
				if (logger.isDebugEnabled()) {
					logger.debug("No default init method named '" + initMethodName +
							"' found on bean with name '" + beanName + "'");
				}
				// Ignore non-existent default lifecycle methods.
				return;
			}
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Invoking init method  '" + initMethodName + "' on bean with name '" + beanName + "'");
		}

		if (System.getSecurityManager() != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				ReflectionUtils.makeAccessible(initMethod);
				return null;
			});
			try {
				AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () ->
					initMethod.invoke(bean), getAccessControlContext());
			}
			catch (PrivilegedActionException pae) {
				InvocationTargetException ex = (InvocationTargetException) pae.getException();
				throw ex.getTargetException();
			}
		}
		else {
			try {
				ReflectionUtils.makeAccessible(initMethod);
				initMethod.invoke(bean);//通过反射调用方法
			}
			catch (InvocationTargetException ex) {
				throw ex.getTargetException();
			}
		}
	}
最后调用后处理器applyBeanPostProcessorsAfterInitialization

	@Override
	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
			Object current = beanProcessor.postProcessAfterInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}

后处理器会依次调用BeanPostProcessor的postProcessAfterInitialization方法。

下面看一下Bean的销毁的过程,见AbstractApplicationContext类的doClose方法:

	protected void doClose() {
		if (this.active.get() && this.closed.compareAndSet(false, true)) {
			if (logger.isInfoEnabled()) {
				logger.info("Closing " + this);
			}

			LiveBeansView.unregisterApplicationContext(this);

			try {
				// Publish shutdown event.
				publishEvent(new ContextClosedEvent(this));
			}
			catch (Throwable ex) {
				logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
			}

			// Stop all Lifecycle beans, to avoid delays during individual destruction.
			if (this.lifecycleProcessor != null) {
				try {
					this.lifecycleProcessor.onClose();
				}
				catch (Throwable ex) {
					logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
				}
			}

			// Destroy all cached singletons in the context's BeanFactory.
			destroyBeans();

			// Close the state of this context itself.
			closeBeanFactory();

			// Let subclasses do some final clean-up if they wish...
			onClose();

			this.active.set(false);
		}
	}

destroyBeans方法,对Bean进行销毁处理。最终在DisposableBeanAdapter的destroy方法实现。

	public void destroy() {
		if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
			for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
				processor.postProcessBeforeDestruction(this.bean, this.beanName);
			}
		}

		if (this.invokeDisposableBean) {
			if (logger.isDebugEnabled()) {
				logger.debug("Invoking destroy() on bean with name '" + this.beanName + "'");
			}
			try {
				if (System.getSecurityManager() != null) {
					AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
						((DisposableBean) bean).destroy();
						return null;
					}, acc);
				}
				else {
					((DisposableBean) bean).destroy();
				}
			}
			catch (Throwable ex) {
				String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
				if (logger.isDebugEnabled()) {
					logger.warn(msg, ex);
				}
				else {
					logger.warn(msg + ": " + ex);
				}
			}
		}

		if (this.destroyMethod != null) {
			invokeCustomDestroyMethod(this.destroyMethod);
		}
		else if (this.destroyMethodName != null) {
			Method methodToCall = determineDestroyMethod(this.destroyMethodName);
			if (methodToCall != null) {
				invokeCustomDestroyMethod(methodToCall);
			}
		}
	}

这里可以看到对Bean的销毁过程,首先对postProcessBeforeDestruction进行调用,然后调用Bean的destroy方法,需要实现DisposableBean接口,最后对Bean的自定义销毁方法的调用。

<think>首先,用户的问题是关于Spring框架中为Bean声明初始化销毁方法的代码应该放在哪里合适。用户提供了几个引用,我需要参考这些引用来回答。 引用内容总结: - [^1]: 提到了多种初始化方式:PostConstruct注解、实现InitializingBean接口、声明init-method方法。销毁方式类似:PreDestroy注解、实现DisposableBean接口、声明destroy-method方法。还讨论了不同作用域的销毁行为。 - [^2]: 讨论了在XML配置中使用default-init-methoddefault-destroy-method属性来指定默认的初始化销毁方法。 - [^3]: 展示了通过实现InitializingBeanDisposableBean接口来自定义初始化销毁方法。 - [^4]: 提到了BeanPostProcessor接口,用于在初始化前后执行操作,但不直接是声明初始化销毁方法。 - [^5]: 在XML配置中,使用init-methoddestroy-method属性指定方法。 用户的问题:为Bean声明初始化销毁方法的代码应该放在哪里合适? 关键点:代码的位置取决于使用的声明方式。Spring提供了多种方式: 1. **XML配置**:在<bean>元素中使用init-methoddestroy-method属性。 2. **注解**:使用@PostConstruct@PreDestroy注解。 3. **接口实现**:实现InitializingBean接口(定义afterPropertiesSet方法)DisposableBean接口(定义destroy方法)。 4. **Java配置**:在@Bean注解中使用initMethoddestroyMethod属性。 我需要根据引用内容,提供清晰的回答。回答应该结构化,逐步解释。 结构回答: 1. **介绍**:简要说明Spring中Bean初始化销毁的重要性。 2. **多种方式的位置**: - XML配置:代码放在XML文件中。 - 注解:代码放在Bean类中。 - 接口实现:代码放在Bean类中。 - Java配置:代码放在配置类中。 3. **推荐位置**:基于最佳实践,建议使用哪种方式。 4. **示例**:提供简单的代码示例。 5. **相关问题**:根据系统指令,生成相关问题。 系统指令: - 使用中文回答。 - 行内数学表达式用$...$,但这里可能不需要数学表达式。 - 独立公式用$$...$$,但这里可能不需要。 - LaTeX语法正确。 - 生成相关问题。 - 回答中引用的段落末尾添加引用标识,如[^1]。 在回答中引用段落: - 当引用特定内容时,添加[^n]。 - 例如,提到XML配置时,引用[^2][^5]。 最终回答结构: - 回答用户问题。 - 生成相关问题。 相关问题生成: - 基于主题:Spring Bean生命周期、初始化销毁方法的最佳实践、不同作用域的影响等。 - 参考系统指令的示例:快速排序的相关问题类似。 相关问题: 1. Spring中Bean初始化的多种方式有哪些? 2. 如何为Bean指定销毁方法? 3. Bean作用域如何影响初始化销毁行为? 4. 使用注解XML配置初始化的优缺点? 5. 如何测试Bean初始化销毁方法? 现在,构建回答。</think>### Spring Bean初始化销毁方法声明位置指南 在Spring框架中,为Bean声明初始化销毁方法的代码位置取决于您使用的配置方式。Spring提供了多种机制来定义这些生命周期方法,包括XML配置、注解接口实现。核心原则是:**初始化销毁方法应在Bean加载时容器销毁时被自动调用,以管理资源(如数据库连接、线程池等)**[^1]。以下我将逐步解释每种方式的最佳位置示例,确保代码可靠且符合Spring最佳实践。 #### 1. **XML配置方式:代码放在XML配置文件中** 如果您使用传统的XML配置,可以在`<bean>`元素中直接声明`init-method``destroy-method`属性。这些属性指定Bean类中的方法名,Spring容器会在Bean初始化销毁前自动调用它们。 - **位置**:代码应写在Spring的XML配置文件(如`applicationContext.xml`)中。 - **优点**:配置与Bean类解耦,便于集中管理。 - **缺点**:需手动维护XML文件,不适合大型项目。 - **示例**: ```xml <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-init-method="init" <!-- 可选:设置默认初始化方法 --> default-destroy-method="destroy"> <!-- 可选:设置默认销毁方法 --> <bean id="myBean" class="com.example.MyBean" init-method="customInit" <!-- 指定初始化方法 --> destroy-method="customDestroy"> <!-- 指定销毁方法 --> </bean> </beans> ``` 在Bean类中,只需定义这些方法: ```java public class MyBean { public void customInit() { System.out.println("Bean初始化..."); } public void customDestroy() { System.out.println("Bean销毁..."); } } ``` **引用说明**:这种方式允许您为多个Bean设置默认方法,减少重复代码[^2][^5]。 #### 2. **注解方式:代码放在Bean类中** 使用Java注解是最现代的方式,直接在Bean类的方法上添加`@PostConstruct`(初始化`@PreDestroy`(销毁)注解。 - **位置**:代码写在Bean类的具体方法上,无需额外配置。 - **优点**:简洁、类型安全,与Spring的依赖注入无缝集成。 - **缺点**:需在类路径中包含`javax.annotation`依赖(如Jakarta EE或Java SE)。 - **示例**: ```java import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; public class MyBean { @PostConstruct public void init() { System.out.println("Bean初始化完成..."); } @PreDestroy public void cleanup() { System.out.println("Bean销毁前清理..."); } } ``` **引用说明**:注解方式在Bean创建后立即执行,适用于单例或原型作用域,且不依赖XML[^1][^4]。 #### 3. **接口实现方式:代码放在Bean类中** 通过实现Spring的`InitializingBean``DisposableBean`接口,在Bean类中重写`afterPropertiesSet()`(初始化`destroy()`(销毁)方法。 - **位置**:代码写在Bean类中,实现接口方法。 - **优点**:强制实现生命周期方法,避免方法名错误。 - **缺点**:紧耦合到Spring API,不推荐在新项目中使用。 - **示例**: ```java import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; public class MyBean implements InitializingBean, DisposableBean { @Override public void afterPropertiesSet() throws Exception { System.out.println("Bean属性设置后初始化..."); } @Override public void destroy() throws Exception { System.out.println("Bean销毁..."); } } ``` **引用说明**:这种方式在Bean依赖注入完成后执行初始化,适用于需要严格顺序控制的场景[^1][^3]。 #### 4. **Java配置方式:代码放在配置类中** 如果您使用基于Java的配置(如`@Configuration`类),可以在`@Bean`注解中指定`initMethod``destroyMethod`属性。 - **位置**:代码写在Spring配置类中(如`AppConfig.java`)。 - **优点**:类型安全,支持现代Spring Boot应用。 - **缺点**:需手动指定方法名,可能引入拼写错误。 - **示例**: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean(initMethod = "start", destroyMethod = "stop") public MyBean myBean() { return new MyBean(); } } public class MyBean { public void start() { System.out.println("Bean启动..."); } public void stop() { System.out.println("Bean停止..."); } } ``` #### 推荐位置最佳实践 - **最佳位置**:优先使用**注解方式**(`@PostConstruct`/`@PreDestroy`),因为它简洁、标准化,且与框架无关[^1][^4]。对于遗留项目,XML配置仍有效。 - **关键考虑**: - **作用域影响**:单例Bean销毁只在容器关闭时触发;原型Bean销毁需手动管理;request/session作用域在作用域结束时自动销毁[^1]。 - **错误处理**:确保方法无参数且返回`void`,否则Spring会抛出异常。 - **测试**:使用JUnit测试容器生命周期,验证方法调用。 - **总体建议**:将代码放在Bean类中(注解或接口)以提高可读性,避免XML配置的维护负担。Spring的IoC容器会自动处理这些方法的调用顺序[^1][^5]。 #### 示例整合 以下是一个完整示例,展示注解方式: ```java // Bean类:代码放在Bean中 @Component public class DatabaseService { @PostConstruct public void initConnection() { System.out.println("数据库连接初始化..."); } @PreDestroy public void closeConnection() { System.out.println("数据库连接关闭..."); } } ``` 在Spring Boot应用中,无需额外配置,容器启动时自动调用`initConnection()`,关闭时调用`closeConnection()`。 如果您有进一步的具体场景(如作用域或依赖问题),请提供细节,我可以更针对性地解答。 ### 相关问题 1. Spring中Bean初始化方法有哪些执行顺序? 2. 如何为不同作用域的Bean(如原型或session)配置销毁方法? 3. 使用注解`@PostConstruct`实现`InitializingBean`接口有什么区别? 4. Spring如何管理Bean生命周期中的异常处理? 5. 在微服务架构中,Bean初始化销毁的最佳实践是什么? [^1]: Spring中支持在Bean的加载时声明初始化方法,该方法会在Bean对象完成初始化之前进行执行,可以为对象指定一些特定的行为,同样的Bean销毁时,也是支持这个动作的。其中因为对象的作用域不同,销毁的表现形式略有区别。 [^2]: 框架使用元素中的default-init-methoddefault-destroy-method属性提供了灵活地配置这种情况。 [^3]: Dog类的初始化方法...Dog类的销毁方法。 [^4]: BeanPostProcessor是一个接口,这个接口中的postProcessBeforeInitialization、postProcessAfterInitialization方法分别在Bean初始化方法执行的前后执行。 [^5]: 通过destroy-method指定Bean销毁时执行的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值