SpringBoot启动流程分析5--run方法之prepareContext()方法

本文详细解析了SpringBoot2.7版本中`prepareContext`方法的工作原理,包括设置ApplicationContext环境、执行后置处理、初始化器应用、发布事件、加载启动类到BeanDefinitionMap等关键步骤。

SpringBoot启动流程分析5–run方法之prepareContext()方法

一、概述

源码基于SpringBoot 2.7.xx版本

1.1 简介

准备应用上下文方法主要做了以下几件事:

  1. 设置ApplicationContext应用上下文环境对象
  2. 执行ApplicationContext后置处理
  3. 执行ApplicationContext初始化器
  4. 发布ApplicationContext已初始化事件
  5. 添加Bean工厂的后置处理器
  6. 加载 启动类 到Spring应用上下文BeanDefinitionMap中,为后面的spring进行自动化配置做好准备
  7. 发布ApplicationContext已准备完毕事件

二、详解

2.1 准备应用上下文

public class SpringApplication {

    private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
                                ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
                                ApplicationArguments applicationArguments, Banner printedBanner) {
        // 设置ApplicationContext应用上下文环境对象
        context.setEnvironment(environment);
        // 执行ApplicationContext后置处理
        postProcessApplicationContext(context);
        // 执行ApplicationContext初始化器
        // 即最开始加载的ApplicationContextInitializer中的initialize方法,向context中添加BeanFactoryPostProcessor、监听器等
        applyInitializers(context);
        // 发布ApplicationContext已初始化事件
        listeners.contextPrepared(context);
        // 执行关闭BootstrapContextCloseEvent类型的监听器
        bootstrapContext.close(context);
        // 开启日志功能
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        // Add boot specific singleton beans
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 将main函数中的args参数封装成单例Bean,注册进容器
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            // 将 printedBanner 也封装成单例,注册进容器
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
            // 设置bean之间的循环引用,默认是为 false
            ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
            if (beanFactory instanceof DefaultListableBeanFactory) {
                // 下面这个是是否允许设置相同的bean名称,但是两个bean的定义不同,来让后者定义的bean覆盖前者。默认是允许的,但是这里设置了不允许
                ((DefaultListableBeanFactory) beanFactory)
                        .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
            }
        }
        // 如果是延迟加载,就是getBean的时候才进行加载,那么就设置对应的lazy工厂后置处理器,默认是false
        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
        // 添加Bean工厂的后置处理器
        context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        // 加载启动类到Spring应用上下文BeanDefinitionMap中,为后面的spring进行自动化配置做好准备
        load(context, sources.toArray(new Object[0]));
        // 发布ApplicationContext已准备完毕事件
        listeners.contextLoaded(context);
    }
}

2.2 加载启动类

public class SpringApplication {

    protected void load(ApplicationContext context, Object[] sources) {
        if (logger.isDebugEnabled()) {
            logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
        }
        // 创建 BeanDefinitionLoader 
        BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
        if (this.beanNameGenerator != null) {
            loader.setBeanNameGenerator(this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            loader.setResourceLoader(this.resourceLoader);
        }
        if (this.environment != null) {
            loader.setEnvironment(this.environment);
        }
        loader.load();
    }

    private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
        // 这里将我们前文创建的应用上下文强转为BeanDefinitionRegistry,他们之间是有继承关系的。
        // BeanDefinitionRegistry定义了很重要的方法registerBeanDefinition(),该方法将BeanDefinition注册进DefaultListableBeanFactory
        // 容器的beanDefinitionMap中。
        if (context instanceof BeanDefinitionRegistry) {
            return (BeanDefinitionRegistry) context;
        }
        if (context instanceof AbstractApplicationContext) {
            return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
        }
        throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
    }
}

2.2.1 createBeanDefinitionLoader()

public class SpringApplication {

    protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
        return new BeanDefinitionLoader(registry, sources);
    }
}

class BeanDefinitionLoader {

    private final Object[] sources;

    private final AnnotatedBeanDefinitionReader annotatedReader;

    private final AbstractBeanDefinitionReader xmlReader;

    private final BeanDefinitionReader groovyReader;

    private final ClassPathBeanDefinitionScanner scanner;

    BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
        Assert.notNull(registry, "Registry must not be null");
        Assert.notEmpty(sources, "Sources must not be empty");
        this.sources = sources;
        // 注解形式的Bean定义读取器 比如:@Configuration @Bean @Controller @Service @Component等等
        this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
        // XML形式的Bean定义读取器
        this.xmlReader = (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null);
        this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null);
        // 类路径扫描器
        this.scanner = new ClassPathBeanDefinitionScanner(registry);
        // 扫描器添加排除过滤器
        this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
    }

    void load() {
        for (Object source : this.sources) {
            load(source);
        }
    }

    private void load(Object source) {
        Assert.notNull(source, "Source must not be null");
        // 从Class加载
        if (source instanceof Class<?>) {
            load((Class<?>) source);
            return;
        }
        // 从Resource加载
        if (source instanceof Resource) {
            load((Resource) source);
            return;
        }
        // 从Package加载
        if (source instanceof Package) {
            load((Package) source);
            return;
        }
        // 从 CharSequence 加载
        if (source instanceof CharSequence) {
            load((CharSequence) source);
            return;
        }
        throw new IllegalArgumentException("Invalid source type " + source.getClass());
    }

    private void load(Class<?> source) {
        if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
            // Any GroovyLoaders added in beans{} DSL can contribute beans here
            GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
            ((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
        }
        if (isEligible(source)) {
            // 将启动类的 BeanDefinition注册进 beanDefinitionMap
            this.annotatedReader.register(source);
        }
    }
}

2.2.2 load()

当前我们的主类会按Class加载

public class AnnotatedBeanDefinitionReader {

    public void register(Class<?>... componentClasses) {
        for (Class<?> componentClass : componentClasses) {
            registerBean(componentClass);
        }
    }

    public void registerBean(Class<?> beanClass) {
        doRegisterBean(beanClass, null, null, null, null);
    }

    private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
                                    @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
                                    @Nullable BeanDefinitionCustomizer[] customizers) {
        // 将指定的类封装为AnnotatedGenericBeanDefinition
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
        if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
            return;
        }

        abd.setInstanceSupplier(supplier);
        // 获取该类的 scope 属性
        ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
        abd.setScope(scopeMetadata.getScopeName());
        String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));

        AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
        if (qualifiers != null) {
            for (Class<? extends Annotation> qualifier : qualifiers) {
                if (Primary.class == qualifier) {
                    abd.setPrimary(true);
                } else if (Lazy.class == qualifier) {
                    abd.setLazyInit(true);
                } else {
                    abd.addQualifier(new AutowireCandidateQualifier(qualifier));
                }
            }
        }
        if (customizers != null) {
            for (BeanDefinitionCustomizer customizer : customizers) {
                customizer.customize(abd);
            }
        }

        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
        definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        // 将该BeanDefinition注册到IoC容器的beanDefinitionMap中
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
    }
}

public abstract class BeanDefinitionReaderUtils {

    public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {

        // Register bean definition under primary name.
        String beanName = definitionHolder.getBeanName();
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

        // Register aliases for bean name, if any.
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String alias : aliases) {
                registry.registerAlias(beanName, alias);
            }
        }
    }
}

registerBeanDefinition()

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {

        Assert.hasText(beanName, "Bean name must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition must not be null");

        if (beanDefinition instanceof AbstractBeanDefinition) {
            try {
                // 最后一次校验了
                // 对bean的Overrides进行校验,还不知道会在哪处理这些overrides
                ((AbstractBeanDefinition) beanDefinition).validate();
            } catch (BeanDefinitionValidationException ex) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Validation of bean definition failed", ex);
            }
        }
        // 判断是否存在重复名字的bean,之后看允不允许override
        BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
        if (existingDefinition != null) {
            if (!isAllowBeanDefinitionOverriding()) {
                // 如果该类存在,且不允许 Overriding 直接抛出异常
                throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
            } else if (existingDefinition.getRole() < beanDefinition.getRole()) {
                // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
                if (logger.isInfoEnabled()) {
                    logger.info("Overriding user-defined bean definition for bean '" + beanName +
                            "' with a framework-generated bean definition: replacing [" +
                            existingDefinition + "] with [" + beanDefinition + "]");
                }
            } else if (!beanDefinition.equals(existingDefinition)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Overriding bean definition for bean '" + beanName +
                            "' with a different definition: replacing [" + existingDefinition +
                            "] with [" + beanDefinition + "]");
                }
            } else {
                if (logger.isTraceEnabled()) {
                    logger.trace("Overriding bean definition for bean '" + beanName +
                            "' with an equivalent definition: replacing [" + existingDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            // 如果该类存在,且允许 Overriding,直接注册进beanDefinitionMap中
            this.beanDefinitionMap.put(beanName, beanDefinition);
        } else {
            if (hasBeanCreationStarted()) {
                // Cannot modify startup-time collection elements anymore (for stable iteration)
                synchronized (this.beanDefinitionMap) {
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    removeManualSingletonName(beanName);
                }
            } else {
                // Still in startup registration phase
                // 如果仍处于启动注册阶段,注册进beanDefinitionMap
                this.beanDefinitionMap.put(beanName, beanDefinition);
                this.beanDefinitionNames.add(beanName);
                removeManualSingletonName(beanName);
            }
            this.frozenBeanDefinitionNames = null;
        }

        if (existingDefinition != null || containsSingleton(beanName)) {
            resetBeanDefinition(beanName);
        } else if (isConfigurationFrozen()) {
            clearByTypeCache();
        }
    }
}
<think> 我们以初始化SpringApplication和运行SpringApplication两大阶段来分析Spring Boot启动流程。下面详细展开: ### **1. 初始化SpringApplication阶段** 此阶段主要完成应用类型判断、初始化器和监听器加载等准备工作,发生在`SpringApplication`构造函数中[^1][^2]。 #### **1.1 判断应用类型** - 通过`WebApplicationType.deduceFromClasspath()`方法检测类路径下的依赖,确定应用类型: - `WebApplicationType.SERVLET`:存在`javax.servlet.Servlet`和`org.springframework.web.context.ConfigurableWebApplicationContext`类(如Spring MVC) - `WebApplicationType.REACTIVE`:存在`org.springframework.web.reactive.DispatcherHandler`且不存在Servlet相关类(如Spring WebFlux) - `WebApplicationType.NONE`:非Web应用 #### **1.2 加载初始化器(ApplicationContextInitializer)** - 通过`SpringFactoriesLoader.loadFactoryNames()`加载`META-INF/spring.factories`中配置的`ApplicationContextInitializer`实现类[^1],例如: ```properties # spring.factories示例 org.springframework.context.ApplicationContextInitializer=\ com.example.MyInitializer ``` - 初始化器用于在`ApplicationContext`刷新前执行自定义逻辑(如设置环境变量) #### **1.3 加载监听器(ApplicationListener)** - 同样从`spring.factories`加载`ApplicationListener`实现类,用于监听Spring事件(如`ApplicationStartingEvent`)[^1][^2]。 - 常见监听器: - `ConfigFileApplicationListener`:加载`application.properties`配置文件 - `LoggingApplicationListener`:初始化日志系统 --- ### **2. 运行SpringApplication阶段** 此阶段通过`run()`方法执行,包含环境准备、上下文创建与刷新等核心步骤[^1][^2]。 ```java public ConfigurableApplicationContext run(String... args) { // 步骤分解如下 } ``` #### **2.1 创建并启动计时器** - 通过`StopWatch`记录启动耗时,发布`ApplicationStartingEvent`事件 #### **2.2 准备环境(Environment)** - 创建并配置环境对象(`StandardEnvironment`或`StandardServletEnvironment`)[^2] - 加载配置文件(如`application.yml`)、命令行参数(`--server.port=8080`) - 发布`ApplicationEnvironmentPreparedEvent`事件,通知监听器环境就绪 #### **2.3 创建应用上下文(ApplicationContext)** - 根据应用类型创建对应上下文实例: - **Servlet应用**:`AnnotationConfigServletWebServerApplicationContext` - **Reactive应用**:`AnnotationConfigReactiveWebServerApplicationContext` - **非Web应用**:`AnnotationConfigApplicationContext` #### **2.4 准备上下文(Prepare Context)** - 执行初始化器:调用所有`ApplicationContextInitializer.initialize()`方法 - 注册主配置类:将`main()`方法中的主类注册为Bean定义(`AnnotatedGenericBeanDefinition`) - 发布`ApplicationContextInitializedEvent`事件 #### **2.5 刷新上下文(Refresh Context)** 此步骤调用`AbstractApplicationContext.refresh()`方法,包含12个核心子步骤: ```java // 精简版代码结构 public void refresh() { prepareRefresh(); // (1) 准备刷新 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // (2) 获取BeanFactory prepareBeanFactory(beanFactory); // (3) 准备BeanFactory postProcessBeanFactory(beanFactory); // (4) 后处理BeanFactory invokeBeanFactoryPostProcessors(beanFactory); // (5) ★ 执行BeanFactoryPostProcessor registerBeanPostProcessors(beanFactory); // (6) 注册BeanPostProcessor initMessageSource(); // (7) 初始化消息源 initApplicationEventMulticaster(); // (8) 初始化事件广播器 onRefresh(); // (9) ★ Web服务器启动Spring Boot扩展点) registerListeners(); // (10) 注册监听器 finishBeanFactoryInitialization(beanFactory); // (11) ★ 初始化单例Bean finishRefresh(); // (12) 完成刷新 } ``` **关键步骤详解**: - **步骤(5) invokeBeanFactoryPostProcessors**: - 执行`BeanDefinitionRegistryPostProcessor`和`BeanFactoryPostProcessor` - **触发自动配置**:`ConfigurationClassPostProcessor`解析`@SpringBootApplication`中的`@EnableAutoConfiguration`,加载`spring.factories`中的`AutoConfiguration`类[^2] - **步骤(9) onRefresh**: - Spring Boot扩展方法:`ServletWebServerApplicationContext.onRefresh()`启动内嵌Tomcat/Jetty服务器 - 创建`ServletWebServerFactory` Bean并调用`getWebServer()`启动服务 - **步骤(11) finishBeanFactoryInitialization**: - 初始化所有非懒加载的单例Bean(包括控制器、服务等) - 执行依赖注入(如`@Autowired`) #### **2.6 刷新后处理(After Refresh)** - 发布`ApplicationStartedEvent`事件 - 调用`ApplicationRunner`和`CommandLineRunner`的`run()`方法(按`@Order`顺序执行) --- ### **总结流程图** ```mermaid graph TD A[main方法] --> B[初始化SpringApplication] B --> B1[判断应用类型] B --> B2[加载初始化器] B --> B3[加载监听器] B --> C[执行run方法] C --> C1[启动计时器与事件] C --> C2[准备环境] C --> C3[创建上下文] C --> C4[准备上下文] C --> C5[刷新上下文] C5 --> C5_5[12步刷新流程] C --> C6[刷新后处理] C --> C7[调用Runners] ``` 初始化和运行阶段共同完成了Spring Boot的自动化启动,其中刷新上下文(12步)是核心[^1][^2]。 §§相关问题§§ 1. Spring Boot如何通过`spring.factories`实现自动配置的扩展机制? 2. 在刷新上下文的12个步骤中,`BeanFactoryPostProcessor`和`BeanPostProcessor`有什么区别? 3. 如何自定义内嵌Web服务器的启动参数(如端口、上下文路径)? </think> ### Spring Boot启动流程分析:初始化SpringApplication和运行SpringApplication #### 一、**初始化SpringApplication阶段** 此阶段发生在`SpringApplication.run()`调用前,主要完成应用基础配置和环境准备[^1]。 ```java // 伪代码:SpringApplication构造函数核心逻辑 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { // 1. 判断应用类型 this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 2. 加载初始化器(ApplicationContextInitializer) setInitializers(getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 3. 加载监听器(ApplicationListener) setListeners(getSpringFactoriesInstances(ApplicationListener.class)); // 4. 推断主配置类 this.mainApplicationClass = deduceMainApplicationClass(); } ``` **关键步骤详解**: 1. **应用类型判断** - 通过扫描类路径判断应用类型: `WebApplicationType.SERVLET` (如Tomcat) / `REACTIVE` (WebFlux) / `NONE` (非Web应用) - 判断依据:检查是否存在`DispatcherServlet`或`ReactiveWebApplicationContext`类[^2] 2. **加载初始化器(Initializers)** - 从`META-INF/spring.factories`加载所有`ApplicationContextInitializer`实现 - 示例:`DelegatingApplicationContextInitializer`用于初始化环境配置 - 作用:在上下文创建前执行预处理逻辑(如设置系统属性) 3. **加载事件监听器(Listeners)** - 加载`ApplicationListener`实现(如`ConfigFileApplicationListener`) - 核心作用:监听启动事件流(共7种生命周期事件)[^1] 4. **推断主配置类** - 通过线程栈分析确定包含`main()`方法启动--- #### 二、**运行SpringApplication阶段** 通过`run()`方法执行核心启动流程(完整流程约12步)[^2]: ```java public ConfigurableApplicationContext run(String... args) { // 1. 启动计时器 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 2. 准备环境配置 ConfigurableEnvironment environment = prepareEnvironment(); // 3. 创建应用上下文 context = createApplicationContext(); // 4. 上下文预处理 prepareContext(context, environment); // 5. ★核心:刷新上下文(12步) refreshContext(context); // 6. 刷新后处理 afterRefresh(context, args); } ``` **核心步骤详解**: 1. **环境准备(Prepare Environment)** - 加载`application.yml/properties`配置文件 - 解析命令行参数`--server.port=8080` - 发布`ApplicationEnvironmentPreparedEvent`事件[^1] 2. **创建上下文(ApplicationContext)** - 根据应用类型实例化对应上下文: ```java switch (webApplicationType) { case SERVLET: return new AnnotationConfigServletWebServerApplicationContext(); case REACTIVE: return new AnnotationConfigReactiveWebServerApplicationContext(); default: return new AnnotationConfigApplicationContext(); } ``` 3. **上下文预处理(Prepare Context)** - 注册主配置类(`AnnotationConfigApplicationContext.register()`) - 执行初始化器:调用所有`ApplicationContextInitializer.initialize()` - 发布`ApplicationContextInitializedEvent`事件 4. **刷新上下文(Refresh Context)** 执行Spring Framework的12步刷新流程(关键步骤): ```java // AbstractApplicationContext.refresh() invokeBeanFactoryPostProcessors(beanFactory); // 触发自动配置 onRefresh(); // 启动Web服务器 finishBeanFactoryInitialization(beanFactory); // 初始化单例Bean ``` - **自动配置触发点**:`ConfigurationClassPostProcessor`解析`@SpringBootApplication` - **Web服务器启动**:`ServletWebServerApplicationContext.onRefresh()`创建Tomcat实例 5. **刷新后处理(After Refresh)** - 调用`ApplicationRunner`和`CommandLineRunner`实现类 - 发布`ApplicationStartedEvent`事件 - 返回完全初始化的`ApplicationContext` --- ### **两大阶段对比** | **阶段** | **关键操作** | **产出物** | |------------------------|----------------------------------------|-------------------------------------| | **初始化SpringApplication** | 加载初始化器/监听器<br>判断应用类型 | 配置完成的SpringApplication实例 | | **运行run()方法** | 环境准备<br>上下文创建与刷新<br>Runner执行 | 可操作的ApplicationContext上下文 | > **性能提示**:初始化阶段耗时通常占启动总耗时的10%-20%,而刷新上下文(尤其Bean初始化)占60%以上[^2]。优化建议:排除不必要的自动配置(`@SpringBootApplication(exclude=DataSourceAutoConfiguration.class)`)和使用懒加载。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

fanderboy

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

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

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

打赏作者

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

抵扣说明:

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

余额充值