Spring源码解读(一)启动流程分析——AbstractApplicationContext

前言

通过对Spring启动流程源码的分析,能够使我们对spring掌握的更透彻。本篇博文通过5.2.19版本的源码以 new ClassPathXmlApplicationContext("classpath:/spring/applicationContext.xml") 为入口,分析spring的启动过程,主要分析内容会在代码的注释中体现。

代码入口

        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        ac.getBean("");

在调试过程中经常使用这段代码获取springbean,所以就通过ClassPathXmlApplicationContext类入手,分析spring启动加载的流程。

源码分析

ClassPathXmlApplicationContext类的继承和实现关系图

这里需要先熟悉它的继承关系 如 AbstractApplicationContext、AbstractRefreshableConfigApplicationContext,后续可以更好的理解源码中的调用链。

up-a41fc6cdc1fe28a043fd767553633314911.png

ClassPathXmlApplicationContext构造方式——程序入口

classPathXmlApplicationContext中有很多构造方法,我这里用到是传递配置文件路径构造方法。

classPathXmlApplicationContext的父类AbstractRefreshableConfigApplicationContext有configLocations参数 是一个String[]数组,用于存放配置文件的路径。所以拿到一个配置文件后new String[]传递。



	/**
	 * 创建一个新的 ClassPathXmlApplicationContext,从给定的 XML 文件加载定义并自动刷新上下文。
	 */
	public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
	}

	/**
	 * 通过给定的配置文件组,创建上下文对象
     * 根据refresh参数判定,是否刷新所有的bean
	 */
	public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {
		// 调用父类构造
		super(parent);
		// 调用父类的父类AbstractRefreshableConfigApplicationContext.setConfigLocations
		//    1 - 将配置文件路径存储成员变量中 String[] configLocations
		//    2 - 调用org.springframework.core.env.PropertyResolver#resolveRequiredPlaceholders 解析配置文件路径中的 ${} 占位符,将它们替换为相应的值。
		setConfigLocations(configLocations);
		if (refresh) {
			// 这里父类org.springframework.context.support.AbstractApplicationContext的方法
			// 用于刷新整个spring上下文信息,是spring的核心内容
			refresh();
		}
	}

AbstractApplicationContext

它是org.springframework.context.ApplicationContext 接口的抽象实现类,简单地实现通用的上下文功能,具体个性化的功能还需要子类实现抽象方法。

AbstractApplicationContext.refresh()方法分析

refresh方法几乎包含了ApplicationContext的所有功能,其方法内部的执行逻辑可概括为以下几点:

  1. 初始化前的准备工作,如对系统变量环境变量的解析
  2. 初始化BeanFactory,并进行XML读取
  3. 对BeanFactory功能的功能进行填充,如@Autowired就是在这一步增加的支持。
  4. 子类覆盖方法做额外处理,方便后续对框架的扩展
  5. 激活各种BeanFactory处理器
  6. 注册拦截bean创建的bean处理器。
  7. 为上下文初始化消息源。
  8. 初始化消息广播器
  9. 留给子类初始化其他的bean
  10. 查找所有ListenerBean,注册到消息广播器中。
  11. 初始化剩下的单例实例(非惰性)
  12. 完成刷新
	/**
	 * 加锁避免多线程刷新上下文
	 *
	 * refresh()和 destroy()同用一个对象锁锁,避免refresh()和 destroy()方法冲突。
	 * */
	private final Object startupShutdownMonitor = new Object();

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// 准备此上下文以进行刷新。
			prepareRefresh();

			// 通知子类刷新内部bean factory
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// 准备 bean 工厂以在此上下文中使用
			prepareBeanFactory(beanFactory);

			try {
				// 允许在上下文子类中对 bean 工厂进行后处理
				postProcessBeanFactory(beanFactory);

				// 调用在上下文中注册为 bean 的工厂处理器
				invokeBeanFactoryPostProcessors(beanFactory);

				// 注册拦截 bean 创建的 bean 处理器
				registerBeanPostProcessors(beanFactory);

				// 为此上下文初始化消息源。
				initMessageSource();

				// 为此上下文初始化事件多播器
				initApplicationEventMulticaster();

				// 初始化特定上下文子类中的其他特殊bean
				onRefresh();

				// 检查并注册监听器
				registerListeners();

				// 实例化剩余的单例对象
				finishBeanFactoryInitialization(beanFactory);

				// 发布相应的事件
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}
				
				// 销毁已经创建的单例以避免悬空资源
				destroyBeans();
				
				// 重置 active
				cancelRefresh(ex);

				throw ex;
			}

			finally {
				// 刷新缓存
				resetCommonCaches();
			}
		}
	}

refresh方法将复杂的代码流程,拆分为若干个子方法,流程看起来非常清晰,方便阅读,下面再细细分析每一个子方法。

环境准备

AbstractApplicationContext.prepareRefresh()

	/**
	 * 设置上下文对象启动时间、活动标识以及执行属性的初始化
	 * 准备此上下文以进行刷新、设置其启动日期和活动标志以及执行任何属性源的初始化。
	 */
	protected void prepareRefresh() {
		this.startupDate = System.currentTimeMillis();
		this.closed.set(false);
		this.active.set(true);

		if (logger.isDebugEnabled()) {
			if (logger.isTraceEnabled()) {
				logger.trace("Refreshing " + this);
			}
			else {
				logger.debug("Refreshing " + getDisplayName());
			}
		}

		initPropertySources();
		getEnvironment().validateRequiredProperties();

		if (this.earlyApplicationListeners == null) {
			this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
		}
		else {
			this.applicationListeners.clear();
			this.applicationListeners.addAll(this.earlyApplicationListeners);
		}

		this.earlyApplicationEvents = new LinkedHashSet<>();
	}

初始化BeanFactory

AbstractApplicationContext.obtainFreshBeanFactory()

	/**
	 * 通知子类,刷新beanFactory
     *
	 * @see #refreshBeanFactory()
	 * @see #getBeanFactory()
	 */
	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		// 这两个都是抽象方法,供实现类实现具体刷新策略
		refreshBeanFactory();
		return getBeanFactory();
	}

从最上面的类图中得知,ClassPathXmlApplicationContext调用的是AbstractRefreshhableApplicationContext的实现。

    /**
	 * 此实现执行此上下文的底层 bean 工厂的实际刷新,关闭以前的 bean 工厂(如果有)
	 * 并为上下文生命周期的下一阶段初始化一个新的 bean 工厂。
	 */
	@Override
	protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			// 如果有beanFactory, 销毁factory中的bean 清空beanFactory
			destroyBeans();
			closeBeanFactory();
		}
		try {
			// 创建一个新的beanFactory
			// 指定DefaultListableBeanFactory,这是构造bean的核心类,非常重要
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			// 指定当前上下文对象的ID为beanFactory的序列化ID , 允许将此 BeanFactory 从此 id 反序列化回 BeanFactory 对象。
			beanFactory.setSerializationId(getId());
			// 自定义此上下文使用的内部 bean 工厂
			customizeBeanFactory(beanFactory);
			// 将 BeanDefinition 委托给beanFactory
			loadBeanDefinitions(beanFactory);
			this.beanFactory = beanFactory;
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}


	@Override
	public final ConfigurableListableBeanFactory getBeanFactory() {
		// 返回 refreshBeanFactory() 方法创建的 beanFactory
		DefaultListableBeanFactory beanFactory = this.beanFactory;
		if (beanFactory == null) {
			throw new IllegalStateException("BeanFactory not initialized or already closed - " +
					"call 'refresh' before accessing beans via the ApplicationContext");
		}
		return beanFactory;
	}

这段方法中使用到的DefaultListableBeanFactory,是spring构造bean的核心类,非常重要,在后续springbean加载流程分析中会讲到这个类。

功能扩展

主要对以下几个方面进行扩展,具体细节暂不展开

  1. 增加对SpEL语言支持
  2. 增加对属性编辑器的支持
  3. 增加一些内置类,如果Aware实现类
  4. 设置了依赖功能可忽略的接口
  5. 注册一些固定依赖的属性
  6. 增加对AspectJ的支持
  7. 将相关环境变量以及属性注册已单例模式注册

AbstractApplicationContext.prepareBeanFactory()

    /**
	 * 配置工厂的标准上下文特征
	 * 例如上下文的 ClassLoader 和后处理器
	 */
	protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		// 告诉内部 bean 工厂使用上下文类加载器
		beanFactory.setBeanClassLoader(getClassLoader());
		beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
		beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

		// 通过若干Aware的实现类进行上下文回调 配置bean factory
		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 接口未在普通工厂中注册为可解析类型。 MessageSource 作为 bean 注册(并为自动装配找到)。
		beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
		beanFactory.registerResolvableDependency(ResourceLoader.class, this);
		beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
		beanFactory.registerResolvableDependency(ApplicationContext.class, this);
		
		// 将用于检测内部 bean 的早期后处理器注册为 ApplicationListener。
		beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

		// 检测 LoadTimeWeaver 如果找到准备织入
		if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
			beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
			// 为类型匹配设置一个临时 ClassLoader。
			beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
		}

		// 注册一些默认的bean。
		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());
		}
	}

关于这个方法中提到的Aware接口在 Spring Aware接口作用及原理 这篇博文中有讲解。

添加ApplicationContextAwareProcessor处理器

postProcessBeanFactory是一个抽象方法,根据最开始的类图可知,这里调用的AbstractRefreshableWebApplicationContext的实现。此方法的作用是 在标准初始化之后修改应用程序上下文的内部 bean 工厂;所有 bean 定义都将被加载,但还没有 bean 被实例化;这允许在某些 ApplicationContext 实现中注册特殊的 BeanPostProcessors 等。

AbstractRefreshableWebApplicationContext.postProcessBeanFactory

	/**
	 * 在标准初始化之后修改应用程序上下文的内部 bean 工厂。
	 * 所有 bean 定义都将被加载,但还没有 bean 被实例化。
	 * 这允许在某些 ApplicationContext 实现中注册特殊的 BeanPostProcessors 等。
	 */
	@Override
	protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		// 添加了一个后置处理器,ServletContextAwareProcessor
		beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
		beanFactory.ignoreDependencyInterface(ServletContextAware.class);
		beanFactory.ignoreDependencyInterface(ServletConfigAware.class);

		// 注册到上下文对象中
		WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
		WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
	}

BeanFactory后处理

Spring容器允许BeanFactoryPostProcessor在容器实际实例化任何其他的bean之前读取配置的元数据,同时可以进行修改。根据业务需要,可配置多个BeanFactoryPostProcessor,如我们在配置数据库连接池中可能会出现这样的配置内容,这里就是通过BeanFactoryPostProcessor的子类来实现替换效果的。

<bean id="DataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource">
	<property name="driver" value="${jdbc.driver}" />
	<property name="driverUrl" value="${jdbc.url}" />
	<property name="user" value="${jdbc.username}" />
	<property name="password" value="${jdbc.password}" />
</bean>

AbstractApplicationContext.invokeBeanFactoryPostProcessors()

	/**
	 * 实例化并调用所有已注册的 BeanFactoryPostProcessor bean,如果给定,则尊重显式顺序。 
	 * 必须在单例实例化之前调用。
	 * 
	 */
	protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
		// getBeanFactoryPostProcessors 返回将应用于内部 BeanFactory 的 BeanFactoryPostProcessor 列表。默认情况是空的
		PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

		// 同时处理 LoadTimeWeaver 
		if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
			beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
			beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
		}
	}

初始化消息源

initMessageSource()

	/**
	 * 初始化消息源。
	 * 如果没有在此上下文中定义,则使用父级。
	 */
	protected void initMessageSource() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		// beanFactory中是否有消息源对象
		if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
			this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
			if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
				HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
				if (hms.getParentMessageSource() == null) {
					// 如果没有父 MessageSource,则仅将父上下文设置为父 MessageSource
					hms.setParentMessageSource(getInternalParentMessageSource());
				}
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Using MessageSource [" + this.messageSource + "]");
			}
		}
		else {
			// beanFactory没有消息源对象,new一个空的
			DelegatingMessageSource dms = new DelegatingMessageSource();
			dms.setParentMessageSource(getInternalParentMessageSource());
			this.messageSource = dms;
			beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
			if (logger.isTraceEnabled()) {
				logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
			}
		}
	}

初始化ApplicationEventMulticaster

AbstractApplicationContext.initApplicationEventMulticaster()

	/**
	 * 初始化 ApplicationEventMulticaster。
	 * 如果上下文中没有定义,则使用 SimpleApplicationEventMulticaster。
	 */
	protected void initApplicationEventMulticaster() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
			this.applicationEventMulticaster =
					beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
			if (logger.isTraceEnabled()) {
				logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
			}
		}
		else {
			this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
			beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
			if (logger.isTraceEnabled()) {
				logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
						"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
			}
		}
	}

留给子类实现的onRefresh

AbstractRefreshableWebApplicationContext.onRefresh()

重写了父类AbstractApplicationContext的onRefresh()方法


	@Override
	protected void onRefresh() {
		// 为当前应用程序上下文初始化 ThemeSource,
		// 自动检测名为“themeSource”的 bean。如果没有找到这样的 bean,则将使用默认(空)ThemeSource。
		this.themeSource = UiApplicationContextUtils.initThemeSource(this);
	}

注册监听器

添加实现 ApplicationListener 作为侦听器的 bean。不影响其他监听器,可以添加而不是 bean。

AbstractApplicationContext.registerListeners()

    protected void registerListeners() {
		// 首先注册静态指定的监听器。
		// Register statically specified listeners first.
		for (ApplicationListener<?> listener : getApplicationListeners()) {
			getApplicationEventMulticaster().addApplicationListener(listener);
		}
		
		// 不要在此处初始化 FactoryBeans:我们需要保留所有常规 bean, 未初始化让后处理器适用他们
		// Do not initialize FactoryBeans here: We need to leave all regular beans
		// uninitialized to let post-processors apply to them!
		String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
		for (String listenerBeanName : listenerBeanNames) {
			getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
		}
		
		// 至此,已经有了一个事件多播器,发布一个时间
		// Publish early application events now that we finally have a multicaster...
		Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
		this.earlyApplicationEvents = null;
		if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
			for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
				getApplicationEventMulticaster().multicastEvent(earlyEvent);
			}
		}
	}

AbstractApplicationContext.finishBeanFactoryInitialization()

完成此上下文的 bean 工厂的初始化,初始化所有剩余的非懒加载的单例 bean,。

    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
		// 为此上下文初始化转换服务。
		if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
				beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
			beanFactory.setConversionService(
					beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
		}
		// 如果没有 BeanFactoryPostProcessor,则注册一个默认的嵌入值解析器
		if (!beanFactory.hasEmbeddedValueResolver()) {
			beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
		}

		// 尽早初始化 LoadTimeWeaverAware bean,以便尽早注册它们的转换器。
		String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
		for (String weaverAwareName : weaverAwareNames) {
			getBean(weaverAwareName);
		}
		// 停止使用临时 ClassLoader 进行类型匹配。
		beanFactory.setTempClassLoader(null);

		// 允许缓存所有 bean 定义元数据,而不是期望进一步的更改。
		beanFactory.freezeConfiguration();
		// 实例化所有剩余的(非惰性初始化)单例。
		// 这里用到的实际上还是 DefaultListableBeanFactory ,记住这个类,在bean初始化中非常重要
		beanFactory.preInstantiateSingletons();
	}

AbstractApplicationContext.finishRefresh()

如标记spring的活动状态等等。

   protected void finishRefresh() {
		// 清除上下文级别的资源缓存(例如扫描中的 ASM 元数据)。
		clearResourceCaches();

		// 为此上下文初始化生命周期处理器。
		initLifecycleProcessor();

		// 首先将刷新传播到生命周期处理器
		getLifecycleProcessor().onRefresh();

		// 发布最终事件
		publishEvent(new ContextRefreshedEvent(this));
		
		// 加入到beanView中,目前应该在测试阶段
		LiveBeansView.registerApplicationContext(this);
	}

重置缓存

AbstractApplicationContext.resetCommonCaches()

重置 Spring 的常见反射元数据缓存

    protected void resetCommonCaches() {
		ReflectionUtils.clearCache();
		AnnotationUtils.clearCache();
		ResolvableType.clearCache();
		CachedIntrospectionResults.clearClassLoader(getClassLoader());
	}

至此,ClassPathXmlApplicationContext已经创建完成。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`afterRefresh` 是 Spring Boot 启动过程中的一个重要方法,它是在 Spring 应用上下文完成刷新之后被调用的。具体来说,`afterRefresh` 方法是在 `AbstractApplicationContext` 类的 `finishRefresh` 方法中被调用的,代码如下: ```java protected void finishRefresh() { // ... // Initialize lifecycle processor for this context. initLifecycleProcessor(); // Propagate refresh to lifecycle processor first. getLifecycleProcessor().onRefresh(); // Publish the final event. publishEvent(new ContextRefreshedEvent(this)); // Participate in LiveBeansView MBean, if active. if (!isRunning()) { stop(); } // ... // Finally, invoke the afterRefresh callbacks. invokeAfterRefreshCallbacks(); } ``` 在 `afterRefresh` 方法中,Spring Boot 会调用 `ApplicationContext` 中所有实现了 `org.springframework.context.support.ApplicationContextAware` 接口的 Bean 的 `setApplicationContext` 方法,将应用上下文传入这些 Bean 中。这样,这些 Bean 就能够获取到应用上下文,并在需要时使用它。 另外,Spring Boot 还会回调所有实现了 `org.springframework.boot.context.event.ApplicationContextInitializedListener` 接口的 Bean 的 `afterApplicationContextInitialized` 方法,这些 Bean 可以在应用上下文初始化之后进行一些自定义操作。 总之,`afterRefresh` 方法是 Spring Boot 启动过程中非常重要的一个方法,它标志着应用上下文已经完成了初始化,可以进入正常的运行状态了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值