spring 容器加载源文案解释(第二章ContextLoader )

package org.springframework.web.context;


/**
 * 为根应用程序上下文执行实际的初始化工作。
 * Called by {@link ContextLoaderListener}.
 *
 * 在{@code web中查找{@link #CONTEXT_CLASS_PARAM "contextClass"}参数。
 * 返回到{@link org.springframework.web.context.support。如果没有找到XmlWebApplicationContext}。
 * 使用默认的ContextLoader实现,指定的任何上下文类都需要实现 {@link ConfigurableWebApplicationContext}接口。
 * 处理{@link #CONFIG_LOCATION_PARAM "contextConfigLocation"} context-param,并将其值传递给上下文实例,将其解析为多个文件路径,这些路径可以用任意数量的逗号和空格分隔,
 *例如。*”WEB- inf / applicationContext1。xml、web - inf / applicationContext2.xml”。*也支持ant样式的路径模式,
 例如。*WEB- inf /*Context.xml,WEB- inf /spring*。xml”或“web - inf / & # 42; & # 42; / * Context.xml”。
 *如果没有显式指定,上下文实现应该使用默认位置(使用XmlWebApplicationContext: "/WEB-INF/applicationContext.xml")。
 *注意:对于多个配置位置,后面的bean定义将覆盖以前加载的文件中定义的配置位置,至少在使用Spring的一个默认ApplicationContext实现时是这样。可以利用这一点,通过额外的XML文件故意覆盖某些bean定义。
 * 除了加载根应用程序上下文之外,该类还可以选择加载或获取共享的父上下文并将其连接到根应用程序上下文。
 *更多信息请参见{@link #loadParentContext(ServletContext)}方法。
 *从Spring 3.1开始,{@code ContextLoader}支持通过{@link #ContextLoader(WebApplicationContext)}构造函数注入根web应用程序上下文,允许在Servlet 3.0+环境中进行编程配置。
 * See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
 *
 * @author Juergen Hoeller
 * @author Colin Sampaleanu
 * @author Sam Brannen
 * @since 17.02.2003
 * @see ContextLoaderListener
 * @see ConfigurableWebApplicationContext
 * @see org.springframework.web.context.support.XmlWebApplicationContext
 */
public class ContextLoader {

    /**
     * 根WebApplicationContext id的配置参数,用作底层BeanFactory的序列化id: {@value}
     */
    public static final String CONTEXT_ID_PARAM = "contextId";

    /**
     * servlet上下文参数的名称(即,可以指定根上下文的配置位置,否则返回到实现的默认值。
     * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION
     */
    public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";

    /**
     * 根WebApplicationContext实现类使用的配置参数:{@value}
     * @see #determineContextClass(ServletContext)
     */
    public static final String CONTEXT_CLASS_PARAM = "contextClass";

    /**
     * 用于初始化根web应用程序上下文的{@link ApplicationContextInitializer}类的配置参数:{@value}
     * @see #customizeContext(ServletContext, ConfigurableWebApplicationContext)
     */
    public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";

    /**
     * 用于初始化当前应用程序中的所有web应用程序上下文的全局{@link ApplicationContextInitializer}类的配置参数:{@value}
     * @see #customizeContext(ServletContext, ConfigurableWebApplicationContext)
     */
    public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";

    /**
     * 可选的servlet上下文参数(例如。”{@code locatorFactorySelector}”)
     *仅在使用{@link #loadParentContext(ServletContext)的默认实现获取父上下文时使用。
     *指定{@link ContextSingletonBeanFactoryLocator#getInstance(String selector)}方法调用中使用的“选择器”,该方法用于获取获取父上下文的BeanFactoryLocator实例。
     * 默认值是{@code classpath*:beanRefContext。的默认值
     * {@link ContextSingletonBeanFactoryLocator#getInstance()} method.
     * Supplying the "parentContextKey" parameter is sufficient in this case.
     */
    public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";

    /**
     * 可选的servlet上下文参数(例如。,“{@code parentContextKey}”)仅在使用{@link #loadParentContext(ServletContext ServletContext)}的默认实现获取父上下文时使用。
     *指定在{@link BeanFactoryLocator#useBeanFactory(String factoryKey)}方法调用中使用的'factoryKey',从BeanFactoryLocator实例获取父应用程序上下文。
     * 在依赖默认的{@code类路径*:beanRefContext时,提供这个“parentContextKey”参数就足够了。用于候选工厂引用的xml}选择器。
     */
    public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";

    /**
     * 这些字符中的任意数量都被认为是单个init-param字符串值中的多个值之间的分隔符。
     */
    private static final String INIT_PARAM_DELIMITERS = ",; \t\n";

    /**
     * 定义ContextLoader默认策略名称的类路径资源的名称(相对于ContextLoader类)。
     */
    private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";


    private static final Properties defaultStrategies;

    static {
        // 从属性文件加载默认策略实现。
        //目前这是严格的内部的,不打算定制
        //由应用程序开发人员编写。
        try {
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ex) {
            throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
        }
    }


    /**
     * 从(线程上下文)类加载器映射到相应的“当前”WebApplicationContext。
     */
    private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread =
            new ConcurrentHashMap<ClassLoader, WebApplicationContext>(1);

    /**
     * 如果ContextLoader类部署在web应用程序类加载器本身中,则为“当前”WebApplicationContext。
     */
    private static volatile WebApplicationContext currentContext;


    /**
     * 这个加载器管理的根WebApplicationContext实例。
     */
    private WebApplicationContext context;

    /**
     * 通过ContextSingletonBeanFactoryLocator加载父工厂时保存BeanFactoryReference。
     */
    private BeanFactoryReference parentContextRef;

    /** 要应用于上下文的实际ApplicationContextInitializer实例 */
    private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers =
            new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();


    /**
     * 创建一个新的{@code ContextLoader},它将基于“contextClass”和“contextConfigLocation”servlet上下文-params创建一个web应用程序上下文。
     *有关每个类的默认值的详细信息,请参阅类级文档。*当在{@code web内声明{@code }子类为{@code }时,通常使用这个构造函数。
     作为一个无参数构造函数是必需的。创建的应用程序上下文将注册到ServletContext属性名{@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}下,子类可以在容器关闭时调用{@link #closeWebApplicationContext}方法来关闭应用程序上下文。
     * @see #ContextLoader(WebApplicationContext)
     * @see #initWebApplicationContext(ServletContext)
     * @see #closeWebApplicationContext(ServletContext)
     */
    public ContextLoader() {
    }

    /**
     * 使用给定的应用程序上下文创建一个新的{@code ContextLoader}。这个构造函数在Servlet 3.0+环境中非常有用,在Servlet 3.0+环境中,可以通过{@link ServletContext#addListener} API基于实例注册侦听器。上下文可能是,也可能不是{@linkplain ConfigurableApplicationContext#refresh() refresh}。
     如果(a)是{@link ConfigurableWebApplicationContext}的实现,并且(b)已经刷新了not(推荐的方法),那么将发生以下情况:其中一个对象将被分配给它{@code ServletContext}, {@code ServletConfig}对象将被委托给应用程序上下文{@link #customizeContext}将被调用任何通过“contextInitializerClasses”init-param指定的{@link ApplicationContextInitializer}。
     {@link ConfigurableApplicationContext #刷新刷新()}将调用上下文已经刷新或没有实现{@code ConfigurableWebApplicationContext},以上假设下会发生用户执行这些操作(或不)他或她的具体需求。
     *参见{@link org.springframework.web。 用于使用示例的WebApplicationInitializer}。
     *在任何情况下,给定的应用程序上下文将被注册到属性名称下的ServletContext {@link WebApplicationContext # ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE},
     子类可以自由调用{@link # closeWebApplicationContext}方法对容器关闭关闭应用程序上下文。
     * @param context the application context to manage
     * @see #initWebApplicationContext(ServletContext)
     * @see #closeWebApplicationContext(ServletContext)
     */
    public ContextLoader(WebApplicationContext context) {
        this.context = context;
    }


    /**
     * 指定应该使用哪个{@link ApplicationContextInitializer}实例初始化这个{@code ContextLoader}使用的应用程序上下文。
     * @since 4.2
     * @see #configureAndRefreshWebApplicationContext
     * @see #customizeContext
     */
    @SuppressWarnings("unchecked")
    public void setContextInitializers(ApplicationContextInitializer<?>... initializers) {
        if (initializers != null) {
            for (ApplicationContextInitializer<?> initializer : initializers) {
                this.contextInitializers.add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer);
            }
        }
    }


    /**
     * 使用在构建时提供的应用程序上下文,或者根据上下文-params“{@link #CONTEXT_CLASS_PARAM contextClass}”和“{@link #CONFIG_LOCATION_PARAM contextConfigLocation}”为给定的servlet上下文初始化Spring的web应用程序上下文。
     * @param servletContext current servlet context
     * @return the new WebApplicationContext
     * @see #ContextLoader(WebApplicationContext)
     * @see #CONTEXT_CLASS_PARAM
     * @see #CONFIG_LOCATION_PARAM
     */
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            throw new IllegalStateException(
                    "Cannot initialize context because there is already a root application context present - " +
                    "check whether you have multiple ContextLoader* definitions in your web.xml!");
        }

        Log logger = LogFactory.getLog(ContextLoader.class);
        servletContext.log("Initializing Spring root WebApplicationContext");
        if (logger.isInfoEnabled()) {
            logger.info("Root WebApplicationContext: initialization started");
        }
        long startTime = System.currentTimeMillis();

        try {
            // 将上下文存储在本地实例变量中,以确保这一点
            // 它在ServletContext关闭时可用。
            if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                if (!cwac.isActive()) {
                    // 上下文还没有刷新——>提供了诸如设置父上下文、设置应用程序上下文id等服务
                    if (cwac.getParent() == null) {
                        // 上下文实例是在没有显式的根web应用程序上下文父类(如果有的话)的情况下注入的。
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            }
            else if (ccl != null) {
                currentContextPerThread.put(ccl, this.context);
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
            }
            if (logger.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
            }

            return this.context;
        }
        catch (RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
            throw ex;
        }
        catch (Error err) {
            logger.error("Context initialization failed", err);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
            throw err;
        }
    }

    /**
     * 为这个加载器实例化根WebApplicationContext,如果指定了默认上下文类或自定义上下文类,则实例化根WebApplicationContext。
     * 这个实现期望自定义上下文实现{@link ConfigurableWebApplicationContext}接口。*可以在子类中重写。
     * 此外,在刷新上下文之前调用{@link #customizeContext},允许子类对上下文执行自定义修改。
     * @param sc current servlet context
     * @return the root WebApplicationContext
     * @see ConfigurableWebApplicationContext
     */
    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        Class<?> contextClass = determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                    "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

    /**
     * 返回要使用的WebApplicationContext实现类,如果指定,则返回默认的XmlWebApplicationContext或自定义上下文类。
     * 当前servlet context @返回要使用的WebApplicationContext实现类
     * @see #CONTEXT_CLASS_PARAM
     * @see org.springframework.web.context.support.XmlWebApplicationContext
     */
    protected Class<?> determineContextClass(ServletContext servletContext) {
        String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            }
            catch (ClassNotFoundException ex) {
                throw new ApplicationContextException(
                        "Failed to load custom context class [" + contextClassName + "]", ex);
            }
        }
        else {
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            }
            catch (ClassNotFoundException ex) {
                throw new ApplicationContextException(
                        "Failed to load default context class [" + contextClassName + "]", ex);
            }
        }
    }

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            // 应用程序上下文id仍然设置为其原始默认值,并根据可用信息分配更有用的id
            String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
            if (idParam != null) {
                wac.setId(idParam);
            }
            else {
                // 生成默认id…
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }

        wac.setServletContext(sc);
        String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam);
        }

        // T在任何情况下,当上下文被刷新时,都会调用wac环境的#initPropertySources;
        // 为了确保servlet属性源在#refresh之前的任何后处理或初始化中都处于适当的位置,这里是否需要立即执行此操作
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
        }

        customizeContext(sc, wac);
        wac.refresh();
    }

    /**
     * 自定义这个ContextLoader创建的{@link ConfigurableWebApplicationContext},它是在配置位置被提供给上下文,但是在上下文被刷新为 之前创建的。
     *默认实现{@linkplain # determineContextInitializerClasses (ServletContext)决定}什么(如果有的话)上下文类初始化器指定通过{@linkplain # CONTEXT_INITIALIZER_CLASSES_PARAM上下文初始化参数}和{@linkplain ApplicationContextInitializer #初始化每个}调用与给定的web应用程序上下文。
     *任何{@code applicationcontextinitialalizer}实现{@link org.springframework.core。或用@{@link org.springframework.core.annotation标记。Order Order}将被适当排序。
     * @param sc the current servlet context
     * @param wac the newly created application context
     * @see #CONTEXT_INITIALIZER_CLASSES_PARAM
     * @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext)
     */
    protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
        List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
                determineContextInitializerClasses(sc);

        for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
            Class<?> initializerContextClass =
                    GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
            if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
                throw new ApplicationContextException(String.format(
                        "Could not apply context initializer [%s] since its generic parameter [%s] " +
                        "is not assignable from the type of application context used by this " +
                        "context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),
                        wac.getClass().getName()));
            }
            this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
        }

        AnnotationAwareOrderComparator.sort(this.contextInitializers);
        for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
            initializer.initialize(wac);
        }
    }

    /**
     * R如果{@link #CONTEXT_INITIALIZER_CLASSES_PARAM}指定了实现类,则使用{@link ApplicationContextInitializer}实现类。
     * @param servletContext current servlet context
     * @see #CONTEXT_INITIALIZER_CLASSES_PARAM
     */
    protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
            determineContextInitializerClasses(ServletContext servletContext) {

        List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
                new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();

        String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
        if (globalClassNames != null) {
            for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
                classes.add(loadInitializerClass(className));
            }
        }

        String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
        if (localClassNames != null) {
            for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
                classes.add(loadInitializerClass(className));
            }
        }

        return classes;
    }

    @SuppressWarnings("unchecked")
    private Class<ApplicationContextInitializer<ConfigurableApplicationContext>> loadInitializerClass(String className) {
        try {
            Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
            if (!ApplicationContextInitializer.class.isAssignableFrom(clazz)) {
                throw new ApplicationContextException(
                        "Initializer class does not implement ApplicationContextInitializer interface: " + clazz);
            }
            return (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz;
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
        }
    }

    /**
     * 具有默认实现(可能被子类覆盖)的模板方法,以加载或获取将用作根WebApplicationContext父上下文的ApplicationContext实例。
     * 如果方法的返回值为null,则不设置父上下文。这里加载父上下文的主要原因是允许多个根web应用程序上下文都是共享EAR上下文的子上下文,或者也可以共享ejb可见的相同父上下文。
     * 对于纯web应用程序,通常不需要担心根web应用程序上下文的父上下文。
     *默认实现使用{@link org.springframework.context.access。     ContextSingletonBeanFactoryLocator},
     通过{@link #LOCATOR_FACTORY_SELECTOR_PARAM}和{@link #LOCATOR_FACTORY_KEY_PARAM}配置,加载一个父上下文,
     它将由ContextSingletonBeanFactoryLocator的所有其他用户共享,这些用户也使用相同的配置参数。
     * @param servletContext current servlet context
     * @return the parent application context, or {@code null} if none
     * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator
     */
    protected ApplicationContext loadParentContext(ServletContext servletContext) {
        ApplicationContext parentContext = null;
        String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
        String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);

        if (parentContextKey != null) {
            // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
            BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
            Log logger = LogFactory.getLog(ContextLoader.class);
            if (logger.isDebugEnabled()) {
                logger.debug("Getting parent context definition: using parent context key of '" +
                        parentContextKey + "' with BeanFactoryLocator");
            }
            this.parentContextRef = locator.useBeanFactory(parentContextKey);
            parentContext = (ApplicationContext) this.parentContextRef.getFactory();
        }

        return parentContext;
    }

    /**
     * 为给定的servlet上下文关闭Spring的web应用程序上下文。
     * 如果使用ContextSingletonBeanFactoryLocator的默认{@link #loadParentContext(ServletContext)实现加载了任何共享父上下文,那么释放对该共享父上下文的一个引用。
     * 如果覆盖{@link #loadParentContext(ServletContext)},您可能也必须覆盖此方法。
     * @param servletContext the ServletContext that the WebApplicationContext runs in
     */
    public void closeWebApplicationContext(ServletContext servletContext) {
        servletContext.log("Closing Spring root WebApplicationContext");
        try {
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ((ConfigurableWebApplicationContext) this.context).close();
            }
        }
        finally {
            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = null;
            }
            else if (ccl != null) {
                currentContextPerThread.remove(ccl);
            }
            servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
            if (this.parentContextRef != null) {
                this.parentContextRef.release();
            }
        }
    }


    /**
     * 获取当前线程的Spring根web应用程序上下文(即当前线程的上下文类加载器,它需要是web应用程序的类加载器)。

     * @ @返回当前根web应用程序上下文,如果没有找到,则返回{@code null}
     * @see org.springframework.web.context.support.SpringBeanAutowiringSupport
     */
    public static WebApplicationContext getCurrentWebApplicationContext() {
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl != null) {
            WebApplicationContext ccpt = currentContextPerThread.get(ccl);
            if (ccpt != null) {
                return ccpt;
            }
        }
        return currentContext;
    }

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值