springMVC源码分析--容器初始化(一)ContextLoaderListener

此文为转载,地址:http://blog.csdn.net/qq924862077/

spring Web中,需要初始化IOC容器,用于存放我们注入的各种对象。当tomcat启动时首先会初始化一个web对应的IOC容器,用于初始化和注入各种我们在web运行过程中需要的对象。当tomcat启动的时候是如何初始化IOC容器的,我们先看一下在web.xml中经常看到的配置:

[html]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <context-param>  
  2.     <param-name>contextCon</param-name>  
  3.     <param-value>  
  4.         classpath:applicationContext.xml  
  5.     </param-value>  
  6. </context-param>  
  7. <listener>  
  8.     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  9. </listener>  
ContextLoaderListener是一个监听器,其实现了ServletContextListener接口,其用来监听Servlet,当tomcat启动时会初始化一个Servlet容器,这样 ContextLoaderListener 会监听到Servlet的初始化,这样在Servlet初始化之后我们就可以在 ContextLoaderListener 中也进行一些初始化操作。看下面的 ServletContextListener 的源码也是比较简单的, ServletContextListener 实现了ServletContextListener接口,所以会有两个方法contextInitialized和contextDestroyed。web容器初始化时会调用方法 contextInitialized ,web容器销毁时会调用方法 contextDestroyed。

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public class ContextLoaderListener extends ContextLoader implements ServletContextListener {  
  2.   
  3.     public ContextLoaderListener() {  
  4.     }  
  5.   
  6.     public ContextLoaderListener(WebApplicationContext context) {  
  7.         super(context);  
  8.     }  
  9.     /** 
  10.      * Initialize the root web application context. 
  11.      */  
  12.     @Override  
  13.     public void contextInitialized(ServletContextEvent event) {  
  14.         //在父类ContextLoader中实现  
  15.         initWebApplicationContext(event.getServletContext());  
  16.     }  
  17.     /** 
  18.      * Close the root web application context. 
  19.      */  
  20.     @Override  
  21.     public void contextDestroyed(ServletContextEvent event) {  
  22.         closeWebApplicationContext(event.getServletContext());  
  23.         ContextCleanupListener.cleanupAttributes(event.getServletContext());  
  24.     }  
  25.   
  26. }  

ContextLoaderListener的方法contextInitialized()的默认实现是在他的父类ContextLoader的initWebApplicationContext方法中实现的,意思就是初始化web应用上下文。他的主要流程就是创建一个IOC容器,并将创建的IOC容器存到servletContext中,ContextLoader的核心实现如下:

initWebApplicationContext函数:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. //初始化WebApplicationContext,IOC容器  
  2.     public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {  
  3.         //判断web容器中是否已经有WebApplicationContext  
  4.         if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {  
  5.             throw new IllegalStateException(  
  6.                     "Cannot initialize context because there is already a root application context present - " +  
  7.                     "check whether you have multiple ContextLoader* definitions in your web.xml!");  
  8.         }  
  9.   
  10.         Log logger = LogFactory.getLog(ContextLoader.class);  
  11.         servletContext.log("Initializing Spring root WebApplicationContext");  
  12.         if (logger.isInfoEnabled()) {  
  13.             logger.info("Root WebApplicationContext: initialization started");  
  14.         }  
  15.         long startTime = System.currentTimeMillis();  
  16.   
  17.         try {  
  18.             // Store context in local instance variable, to guarantee that  
  19.             // it is available on ServletContext shutdown.  
  20.             //创建WebApplicationContext  
  21.             if (this.context == null) {  
  22.                 //最终得到的是XmlWebApplicationContext  
  23.                 this.context = createWebApplicationContext(servletContext);  
  24.             }  
  25.             if (this.context instanceof ConfigurableWebApplicationContext) {  
  26.                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;  
  27.                 //判断应用上下文是否激活  
  28.                 if (!cwac.isActive()) {  
  29.                     // The context has not yet been refreshed -> provide services such as  
  30.                     // setting the parent context, setting the application context id, etc  
  31.                     //判断是否已经有父容器  
  32.                     if (cwac.getParent() == null) {  
  33.                         // The context instance was injected without an explicit parent ->  
  34.                         // determine parent for root web application context, if any.  
  35.                         //获得父容器  
  36.                         ApplicationContext parent = loadParentContext(servletContext);  
  37.                         cwac.setParent(parent);  
  38.                     }  
  39.                     //设置并刷新WebApplicationContext容器  
  40.                     configureAndRefreshWebApplicationContext(cwac, servletContext);  
  41.                 }  
  42.             }  
  43.             //将初始化的WebApplicationContext设置到servletContext中  
  44.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);  
  45.   
  46.             ClassLoader ccl = Thread.currentThread().getContextClassLoader();  
  47.             if (ccl == ContextLoader.class.getClassLoader()) {  
  48.                 currentContext = this.context;  
  49.             }  
  50.             else if (ccl != null) {  
  51.                 currentContextPerThread.put(ccl, this.context);  
  52.             }  
  53.   
  54.             if (logger.isDebugEnabled()) {  
  55.                 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +  
  56.                         WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");  
  57.             }  
  58.             if (logger.isInfoEnabled()) {  
  59.                 long elapsedTime = System.currentTimeMillis() - startTime;  
  60.                 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");  
  61.             }  
  62.             //初始化 WebApplicationContext完成并返回  
  63.             return this.context;  
  64.         }  
  65.         catch (RuntimeException ex) {  
  66.             logger.error("Context initialization failed", ex);  
  67.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);  
  68.             throw ex;  
  69.         }  
  70.         catch (Error err) {  
  71.             logger.error("Context initialization failed", err);  
  72.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);  
  73.             throw err;  
  74.         }  
  75.     }  

springMVC父容器初始化流程图(盗图)如下:


完整的ContextLoader源码如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * 用于初始化ApplicationContext 
  3.  * 
  4.  */  
  5. public class ContextLoader {  
  6.   
  7.     //WebApplicationContext id  
  8.     public static final String CONTEXT_ID_PARAM = "contextId";  
  9.       
  10.     //配置文件  
  11.     public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";  
  12.   
  13.     public static final String CONTEXT_CLASS_PARAM = "contextClass";  
  14.   
  15.     public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";  
  16.   
  17.       
  18.     public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";  
  19.   
  20.       
  21.     public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";  
  22.   
  23.     public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";  
  24.   
  25.     private static final String INIT_PARAM_DELIMITERS = ",; \t\n";  
  26.   
  27.     private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";  
  28.   
  29.   
  30.     private static final Properties defaultStrategies;  
  31.   
  32.     static {  
  33.         // Load default strategy implementations from properties file.  
  34.         // This is currently strictly internal and not meant to be customized  
  35.         // by application developers.  
  36.         try {  
  37.             ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);  
  38.             defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);  
  39.         }  
  40.         catch (IOException ex) {  
  41.             throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());  
  42.         }  
  43.     }  
  44.     private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread =  
  45.             new ConcurrentHashMap<ClassLoader, WebApplicationContext>(1);  
  46.       
  47.     private static volatile WebApplicationContext currentContext;  
  48.       
  49.     private WebApplicationContext context;  
  50.       
  51.     private BeanFactoryReference parentContextRef;  
  52.   
  53.     private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers =  
  54.             new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();  
  55.   
  56.     public ContextLoader() {  
  57.     }  
  58.   
  59.     public ContextLoader(WebApplicationContext context) {  
  60.         this.context = context;  
  61.     }  
  62.   
  63.     @SuppressWarnings("unchecked")  
  64.     public void setContextInitializers(ApplicationContextInitializer<?>... initializers) {  
  65.         if (initializers != null) {  
  66.             for (ApplicationContextInitializer<?> initializer : initializers) {  
  67.                 this.contextInitializers.add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer);  
  68.             }  
  69.         }  
  70.     }  
  71.   
  72.     //初始化WebApplicationContext,IOC容器  
  73.     public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {  
  74.         //判断web容器中是否已经有WebApplicationContext  
  75.         if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {  
  76.             throw new IllegalStateException(  
  77.                     "Cannot initialize context because there is already a root application context present - " +  
  78.                     "check whether you have multiple ContextLoader* definitions in your web.xml!");  
  79.         }  
  80.   
  81.         Log logger = LogFactory.getLog(ContextLoader.class);  
  82.         servletContext.log("Initializing Spring root WebApplicationContext");  
  83.         if (logger.isInfoEnabled()) {  
  84.             logger.info("Root WebApplicationContext: initialization started");  
  85.         }  
  86.         long startTime = System.currentTimeMillis();  
  87.   
  88.         try {  
  89.             // Store context in local instance variable, to guarantee that  
  90.             // it is available on ServletContext shutdown.  
  91.             //创建WebApplicationContext  
  92.             if (this.context == null) {  
  93.                 //最终得到的是XmlWebApplicationContext  
  94.                 this.context = createWebApplicationContext(servletContext);  
  95.             }  
  96.             if (this.context instanceof ConfigurableWebApplicationContext) {  
  97.                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;  
  98.                 //判断应用上下文是否激活  
  99.                 if (!cwac.isActive()) {  
  100.                     // The context has not yet been refreshed -> provide services such as  
  101.                     // setting the parent context, setting the application context id, etc  
  102.                     //判断是否已经有父容器  
  103.                     if (cwac.getParent() == null) {  
  104.                         // The context instance was injected without an explicit parent ->  
  105.                         // determine parent for root web application context, if any.  
  106.                         //获得父容器  
  107.                         ApplicationContext parent = loadParentContext(servletContext);  
  108.                         cwac.setParent(parent);  
  109.                     }  
  110.                     //设置并刷新WebApplicationContext容器  
  111.                     configureAndRefreshWebApplicationContext(cwac, servletContext);  
  112.                 }  
  113.             }  
  114.             //将初始化的WebApplicationContext设置到servletContext中  
  115.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);  
  116.   
  117.             ClassLoader ccl = Thread.currentThread().getContextClassLoader();  
  118.             if (ccl == ContextLoader.class.getClassLoader()) {  
  119.                 currentContext = this.context;  
  120.             }  
  121.             else if (ccl != null) {  
  122.                 currentContextPerThread.put(ccl, this.context);  
  123.             }  
  124.   
  125.             if (logger.isDebugEnabled()) {  
  126.                 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +  
  127.                         WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");  
  128.             }  
  129.             if (logger.isInfoEnabled()) {  
  130.                 long elapsedTime = System.currentTimeMillis() - startTime;  
  131.                 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");  
  132.             }  
  133.             //初始化 WebApplicationContext完成并返回  
  134.             return this.context;  
  135.         }  
  136.         catch (RuntimeException ex) {  
  137.             logger.error("Context initialization failed", ex);  
  138.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);  
  139.             throw ex;  
  140.         }  
  141.         catch (Error err) {  
  142.             logger.error("Context initialization failed", err);  
  143.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);  
  144.             throw err;  
  145.         }  
  146.     }  
  147.   
  148.       
  149.     //在web容器加载的时候初始化根WebApplicationContext  
  150.     protected WebApplicationContext createWebApplicationContext(ServletContext sc) {  
  151.         //通过反射获得上下文类  
  152.         Class<?> contextClass = determineContextClass(sc);  
  153.         if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {  
  154.             throw new ApplicationContextException("Custom context class [" + contextClass.getName() +  
  155.                     "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");  
  156.         }  
  157.         //初始化WebApplicationContext,是其实现类XmlWebApplicationContext  
  158.         return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);  
  159.     }  
  160.   
  161.       
  162.     protected Class<?> determineContextClass(ServletContext servletContext) {  
  163.         //查看servlet是否已经存在应用上下文类名  
  164.         String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);  
  165.         if (contextClassName != null) {  
  166.             try {  
  167.                 //类名不为空则反射获得类  
  168.                 return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());  
  169.             }  
  170.             catch (ClassNotFoundException ex) {  
  171.                 throw new ApplicationContextException(  
  172.                         "Failed to load custom context class [" + contextClassName + "]", ex);  
  173.             }  
  174.         }  
  175.         else {  
  176.             //如果应用上下文类名为空,则获得默认的context上下文名  
  177.             contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());  
  178.             try {  
  179.                 return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());  
  180.             }  
  181.             catch (ClassNotFoundException ex) {  
  182.                 throw new ApplicationContextException(  
  183.                         "Failed to load default context class [" + contextClassName + "]", ex);  
  184.             }  
  185.         }  
  186.     }  
  187.   
  188.     protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {  
  189.         if (ObjectUtils.identityToString(wac).equals(wac.getId())) {  
  190.             // The application context id is still set to its original default value  
  191.             // -> assign a more useful id based on available information  
  192.             String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);  
  193.             //设置应用上下文id,在web.xml中可以进行配置  
  194.             if (idParam != null) {  
  195.                 wac.setId(idParam);  
  196.             }  
  197.             else {  
  198.                 // Generate default id...  
  199.                 //如果没有配置则设置默认名称  
  200.                 wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +  
  201.                         ObjectUtils.getDisplayString(sc.getContextPath()));  
  202.             }  
  203.         }  
  204.         //设置ServletContext到容器中  
  205.         wac.setServletContext(sc);  
  206.         //获得web.xml中配置的contextConfigLocation的值,一般是classpath:applicationContext.xml  
  207.         String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);  
  208.         if (configLocationParam != null) {  
  209.             //设置加载文件地址  
  210.             wac.setConfigLocation(configLocationParam);  
  211.         }  
  212.   
  213.         // The wac environment's #initPropertySources will be called in any case when the context  
  214.         // is refreshed; do it eagerly here to ensure servlet property sources are in place for  
  215.         // use in any post-processing or initialization that occurs below prior to #refresh  
  216.         ConfigurableEnvironment env = wac.getEnvironment();  
  217.         if (env instanceof ConfigurableWebEnvironment) {  
  218.             ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);  
  219.         }  
  220.   
  221.         customizeContext(sc, wac);  
  222.         //这一步是特别关键的,这样application.xml中的所有配置都会被初始化到XMLWebApplication中  
  223.         wac.refresh();  
  224.     }  
  225.   
  226.     protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {  
  227.         List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =  
  228.                 determineContextInitializerClasses(sc);  
  229.   
  230.         for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {  
  231.             Class<?> initializerContextClass =  
  232.                     GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);  
  233.             if (initializerContextClass != null) {  
  234.                 Assert.isAssignable(initializerContextClass, wac.getClass(), String.format(  
  235.                         "Could not add context initializer [%s] since its generic parameter [%s] " +  
  236.                         "is not assignable from the type of application context used by this " +  
  237.                         "context loader [%s]: ", initializerClass.getName(), initializerContextClass.getName(),  
  238.                         wac.getClass().getName()));  
  239.             }  
  240.             this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));  
  241.         }  
  242.   
  243.         AnnotationAwareOrderComparator.sort(this.contextInitializers);  
  244.         for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {  
  245.             initializer.initialize(wac);  
  246.         }  
  247.     }  
  248.   
  249.     protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>  
  250.             determineContextInitializerClasses(ServletContext servletContext) {  
  251.   
  252.         List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =  
  253.                 new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();  
  254.   
  255.         String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);  
  256.         if (globalClassNames != null) {  
  257.             for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {  
  258.                 classes.add(loadInitializerClass(className));  
  259.             }  
  260.         }  
  261.   
  262.         String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);  
  263.         if (localClassNames != null) {  
  264.             for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {  
  265.                 classes.add(loadInitializerClass(className));  
  266.             }  
  267.         }  
  268.   
  269.         return classes;  
  270.     }  
  271.   
  272.     @SuppressWarnings("unchecked")  
  273.     private Class<ApplicationContextInitializer<ConfigurableApplicationContext>> loadInitializerClass(String className) {  
  274.         try {  
  275.             Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());  
  276.             Assert.isAssignable(ApplicationContextInitializer.class, clazz);  
  277.             return (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz;  
  278.         }  
  279.         catch (ClassNotFoundException ex) {  
  280.             throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);  
  281.         }  
  282.     }  
  283.   
  284.       
  285.     //从ServletContext中获取父容器  
  286.     protected ApplicationContext loadParentContext(ServletContext servletContext) {  
  287.         ApplicationContext parentContext = null;  
  288.         String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);  
  289.         String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);  
  290.   
  291.         if (parentContextKey != null) {  
  292.             // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"  
  293.             BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);  
  294.             Log logger = LogFactory.getLog(ContextLoader.class);  
  295.             if (logger.isDebugEnabled()) {  
  296.                 logger.debug("Getting parent context definition: using parent context key of '" +  
  297.                         parentContextKey + "' with BeanFactoryLocator");  
  298.             }  
  299.             this.parentContextRef = locator.useBeanFactory(parentContextKey);  
  300.             parentContext = (ApplicationContext) this.parentContextRef.getFactory();  
  301.         }  
  302.   
  303.         return parentContext;  
  304.     }  
  305.   
  306.       
  307.     //通过给定的ServletContext来关闭IOC容器  
  308.     public void closeWebApplicationContext(ServletContext servletContext) {  
  309.         servletContext.log("Closing Spring root WebApplicationContext");  
  310.         try {  
  311.             if (this.context instanceof ConfigurableWebApplicationContext) {  
  312.                 ((ConfigurableWebApplicationContext) this.context).close();  
  313.             }  
  314.         }  
  315.         finally {  
  316.             ClassLoader ccl = Thread.currentThread().getContextClassLoader();  
  317.             if (ccl == ContextLoader.class.getClassLoader()) {  
  318.                 currentContext = null;  
  319.             }  
  320.             else if (ccl != null) {  
  321.                 currentContextPerThread.remove(ccl);  
  322.             }  
  323.             servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);  
  324.             if (this.parentContextRef != null) {  
  325.                 this.parentContextRef.release();  
  326.             }  
  327.         }  
  328.     }  
  329.   
  330.     //在当前线程中获取WebApplicationContext  
  331.     public static WebApplicationContext getCurrentWebApplicationContext() {  
  332.         ClassLoader ccl = Thread.currentThread().getContextClassLoader();  
  333.         if (ccl != null) {  
  334.             WebApplicationContext ccpt = currentContextPerThread.get(ccl);  
  335.             if (ccpt != null) {  
  336.                 return ccpt;  
  337.             }  
  338.         }  
  339.         return currentContext;  
  340.     }  
  341.   
  342. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值