SpringBoot启动原理

Springboot启动过程

        1.创建SpringApplication对象sp,初始化属性,例如资源加载器resourceLoader,启动类mainApplicationClass,webApplicationType类型,初始化器initializers和listeners(通过SpringFactoriesLoader类加载资源到cache)

        2.调用sp.run(args)方法。

                a.EventPublishingRunListener对象调用关于ApplicationStartingEvent对象的监听器。环境准备:其new StandardServletEnvironment对象,对该对象设置args,设置名为random(RandomValuePropertySource)资源。之后EventPublishingRunListener对象调用关于ApplicationEnvironmentPreparedEvent对象的监听器。其中一个ConfigFileApplicationListener作用加载random环境资源对象和OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}资源对象以及加载EnvironmentPostProcessor的增强处理器,并调用。

                b.new AnnotationConfigServletWebServerApplicationContext对象。属性AnnotatedBeanDefinitionReader(作用registerAnnotationConfigProcessors :在beanFactory,初始化ConfigurationClassPostProcessor在内的6个RootBeanDefinition对象)和ClassPathBeanDefinitionScanner。在该类的父类GenericApplicationContext无参构造方法中beanFactory = new DefaultListableBeanFactory()。有一个常用的注册单例方法beanFactory(String var1, Object var2)。初始化context的监听器和beanfactoryPostProcessors,最后EventPublishingRunListener 对象contextPrepared()。

       
         c.beanFactory中的beanDefinitionMap中添加启动类BeanDefinition=AnnotatedGenericBeanDefinition,为Generic bean,其他为root bean。调用所有实现ApplicationContextAware接口的类的setApplicationContext(...)方法,接着EventPublishingRunListener对象调用关于ApplicationPreparedEvent事件对象的监听器。

                d.context.refresh(),中从context中得到beanFactory,并初始化beanFactory。例如注册类加载器,加入BeanPostProcessor,初始化多播器SimpleApplicationEventMulticaster。在context.invokeBeanFactoryPostProcessors()方法中。ConfigurationClassPostProcessor对象.postProcessBeanDefinitionRegistry(registry)该方法中ConfigurationClassParser对象解析beanDefinitionMap中的启动类(AnnotatedGenericBeanDefinition)。扫描启动类的basePackages,ClassPathBeanDefinitionScanner/ConfigurationClassBeanDefinitionReader

类对象注册包中的类到beanDefinitionMap()。创建tomcatWebServer,之后发布上下文已经刷新事件,启动tomcat,发布servletwebserver已经被初始化事件。

              e.context.publishEvent(ApplicationStartedEvent) 上下文对象发布应用启动完成事件。

              f.调用ApplicationRunner和CommandLineRunner接口的实现类。context.publishEvent(ApplicationReadyEvent) 发布应用准备完成事件。

循环依赖   

        假设类a属性有b,b类属性有a。

        1,创建a时,getBean(a)->doGetBean->doGetBean方法中:a.getSingleton(String beanName)判断(一级缓存singletonObjects取null && isSingletonCurrentlyInCreation(beanName))条件为false,返回null。b.getSingleton(String beanName, ObjectFactory<?> singletonFactory)   先a加入singletonsCurrentlyInCreation集合。调用doCreateBean(...)方法:先实例化a(未赋值),将a的name和得到a实例的lambda表达式放入三级缓存singletonFactories中并且清除a在二级缓存earlySingletonObjects的对象。之后populateBean()方法中,会解析a类的属性b,这时会getBean(b)。

        2,创建b时,getBean(b)->doGetBean->doGetBean方法中:a.getSingleton(String beanName)判断(一级缓存singletonObjects取null && isSingletonCurrentlyInCreation(beanName))条件为false,返回null。b.getSingleton(String beanName, ObjectFactory<?> singletonFactory)    先b加入singletonsCurrentlyInCreation集合 ,后调用doCreateBean(...)方法:先实例化b(未赋值),将b的name和得到b实例的lambda表达式放入三级缓存singletonFactories中并且清除b在二级缓存earlySingletonObjects的对象。之后populateBean()方法中,会解析b类的属性a,这时会getBean(a)。

        3.在getBean(a)->doGetBean->doGetBean方法中:a.getSingleton(String beanName)判断(一级缓存singletonObjects取null && isSingletonCurrentlyInCreation(beanName))条件为true,如果二级缓存为null,则从三级缓存获取。执行lambda表达式获取a的lambda表达从三级缓存移出到二级缓存。获取a实例赋值给b实例的a属性。

        4.  将b移除singletonsCurrentlyInCreation集合,然后 b的实例加入一级缓存,b移除二和三级缓存。

        5.将b实例赋值给a实例的属性b。将a移除singletonsCurrentlyInCreation集合,然后 a的实例加入一级缓存,a移除二和三级缓存。

        6.getBean(b)时,直接从一级缓存直接获取。

//调用SpringApplication类的静态方法
ConfigurableApplicationContext run(Class<?> primarySource, String... args)
ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args){
    //构造SpringApplication类对象,
    //调用的SpringApplication的双参构造方法 SpringApplication(ResourceLoader resourceLoader,         
    // Class... primarySources)
    return (new SpringApplication(primarySources)).run(args);
}

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
        //初始化值sources,bannerMode,logStartupInfo,registerShutdownHook,resourceLoader
        //additionalProfiles  = new HashSet(),sources= new LinkedHashSet()
        
       //primarysources 不能为空,为空即启动报错
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        
        //使用枚举 public enum WebApplicationType {NONE,SERVLET,REACTIVE}
        //如果有reactive.DispatcherHandler类则为WebApplicationType.REACTIVE
        //如果有javax.servlet.Servlet和context.ConfigurableWebApplicationContext类则为
        //WebApplicationType.SERVLET
        this.webApplicationType = this.deduceWebApplicationType();


        //1.在spring-boot:版本号.jar和spring-boot-autoconfigure:版本号.jar下
        //  METAINF下spring.factories下
        //  ApplicationContextInitializer组下的6个类,进行实例化,且对实现Order的接口进行排序 
        //2.SpringFactoriesLoader的静态方法loadFactoryNames方法及该方法内            
        //  loadSpringFactories()读取spring.factores的所有内容到cache。然后 
        //  ApplicationContextInitializer组的获取的6个类,
        //3.排序后  1.DelegatingApplicationContextInitializer  
        //       2.ContextIdApplicationContextInitializer  ...等等6个                                      
         this.setInitializers(this.getSpringFactoriesInstances(
ApplicationContextInitializer.class));
        
        //用上述同样的方法,获取METAINF下spring.factories下ApplicationContextInitializer组     
        //下的监听器,进行实例化,且对实现Order的接口进行排序 
        //第一个监听器类ConfigFileApplicationListener
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        
        //得到启动main方法的类的class。
        //通过new RuntimeException()对象,得到StackTraceElement[]对象中是main方法的 
        // StackTraceElement对象,通过StackTraceElement对象.getClassName()得到Class
        this.mainApplicationClass = this.deduceMainApplicationClass();
}


public ConfigurableApplicationContext run(String... args) {

        //StopWatch自带计时工具类。StopWatch.TaskInfo为内部类任务类其属性:     
        //taskName,任务名称。timeMillis 该任务耗时
        //空参构造id=""
        StopWatch stopWatch = new StopWatch();
        //任务名称taskName=""。
        //id="",taskName=""的耗时开始计时
        stopWatch.start();
        
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        
        //向系统System对象配置HeadLess属性
        this.configureHeadlessProperty();
        
        //SpringFactoriesLoader的cache中得到SpringApplicationRunListener组下的监听器
        // 只有一个EventPublishingRunListener由两个重要属性
        //        SpringApplication application=springApplication对象
        // SimpleApplicationEventMulticaster initialMulticaster对象
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        
        //通过调用initialMulticaster对象调用监听器的onApplicationEvent()
        //下面是四个ApplicationStartEvent事件对象对应的监听器
        //LoggingApplicationListener 初始化loggingSystem=LogbackLoggingSystem,
        //  并且loggingSystem.beforeInitialize()->TurboFilterList表中添加FilterReply.DENY
        //BackgroundPreinitializer  开启另为线程后台完成
        //   ConversionServiceInitializer(),ValidationInitializer()
        //   MessageConverterInitializer(),CharsetInitializer()等   
        //DelegatingApplicationListener  doNothing
        //LiquibaseServiceLocatorApplicationListener doNothing
        listeners.starting();

        Collection exceptionReporters;
        try { 
            //封装args
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            
            //1.根据webApplicationType=WebApplicationType.SERVLET创建 
            //StandardServletEnvironment对象,否则创建StandardEnvironment对象
            //StandardServletEnvironment对象属性propertySource有
            //   servletConfigInitParams,servletContextInitParams,
            //   systemProperties,systemEnvironment,commandLineArgs,applicationConfig: 
            //   [classpath:/application.properties]
            //2.EventPublishingRunListener对象environmentPrepared(environment)方法
            //  内部调用 
            //initialMulticaster.multicastEvent(ApplicationEnvironmentPreparedEvent)
            // 对应ConfigFileApplicationListener,AnsiOutputApplicationListener
            // LoggingApplicationListener,ClasspathLoggingApplicationListener
            //BackgroundPreinitializer 等等
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            
            //向System对象设置spring.beaninfo.ignore=true
            this.configureIgnoreBeanInfo(environment);

            //如果bannerMode != Mode.OFF,则使用默认Banner为SpringBootBanner
            Banner printedBanner = this.printBanner(environment);
            
            //如果webApplicationType=SERVLET,则
            // 利用Class.forName(web.servlet.context.AnnotationConfigServletWebServer     
            //       ApplicationContext)空参构造创建对象,同时初始化this.refreshed=false
            //       和this.beanFactory = new DefaultListableBeanFactory()和this.
            //       ignoreDependencyInterface=BeanNameAware/BeanFactoryAware/
            //       BeanClassLoaderAware.class等等                        
            // 1.初始化this.reader = new AnnotatedBeanDefinitionReader(this);
            //      初始化this.beanNameGenerator = new AnnotationBeanNameGenerator()
            //      初始化this.scopeMetadataResolver = new 
            //                                     AnnotationScopeMetadataResolver()
            //      初始化this.registry = registry(BeanDefinitionRegistry);
            //      初始化this.conditionEvaluator = new ConditionEvaluator(registry, 
            //                                      environment, (ResourceLoader)null)
            //      AnnotationConfigUtils.registerAnnotationConfig
            //                                      Processors(this.registry) 
            //                设置DefaultListableBeanFactory beanFactory
            //                   beanFactory.setDependencyComparator()
            //                   beanFactory.setAutowireCandidateResolver
            //                   beanFactory的beanDefinitionNames
            //                   beanDefinitionMap,value为RootBeanDefinition分别为
            //                       annotation.internalConfigurationAnnotationProcessor
            //                       annotation.internalAutowiredAnnotationProcessor
            //                       annotation.internalRequiredAnnotationProcessor
            //                       annotation.internalCommonAnnotationPrecessor             
            //                       event.internalEventListenerProcessor
            //                       event.internalEventListenerFactory
            // 2.初始化this.scanner = new ClassPathBeanDefinitionScanner(this);
            //      this.beanDefinitionDefaults = new BeanDefinitionDefaults()
            //      this.beanNameGenerator = new AnnotationBeanNameGenerator()
            //      this.scopeMetadataResolver = new AnnotationScopeMetadataResolver()
            //      this.includeAnnotationConfig = true
            //      this.registry = registry;
            //      this.includeFilters=Component,javax.ManagedBean
            //      this.excludeFilters为0
            //      this.setEnvironment
            //      this.setResourceLoader
            context = this.createApplicationContext();
            //从缓存中加载SpringBootExceptionReporter组的类 ,封装成FailureAnalyzers
            //类,其属性List<FailureAnalyzer> analyzers。   
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            
            // 设置AnnotationConfigServletWebServerApplicationContext对象context     
            //   context.setEnvironment(environment) 
            //   SpringApplication对象.postProcessApplicationContext(context)
            //          beanNameGenerator与resourceLoader的设置
            //   SpringApplication对象.applyInitializers(context) 
            //          调用ApplicationContextInitializer接口的实现类的initialize()方法
            //          例如ContextIdApplicationContextInitializer.initialize()方法
            //            为applicationContext设置id=application和注册内部类ContextId
            //          例如ServerPortInfoApplicationContextInitializer.initialize()
            //            为applicationContext设置监听器ServerPortIn...ContextInitializer
            //          例如SharedMetadataReaderFactoryContextInitializer.initialize
            //            为applicationContext.addBeanFactoryPostProcessor
            //            CachingMetadataReaderFactoryPostProcessor对象   
            //   listeners.contextPrepared(context)
            //         使用EventPublishingRunListener.contextPrepared(context) nothing 
            //   打印启动相关日志和配置文件相关日志
            //   向DefaultListableBeanFactory中sinletonObjects注册bean
            //          springApplicationArguments和springBootBanner
            //   Set<Object> sources = this.getAllSources();
            //       将SpringApplication对象中的primarySources(启动类)
            //       和sources,加入sources中
            //   this.load(context, sources.toArray(new Object[0]));
            //       创建BeanDefinitionLoader loader对象,该对象属性
            //           初始化sources=启动类.class
            //           初始化annotatedReader=AnnotatedBeanDefinitionReader对象
            //           初始化scanner=ClassPathBeanDefinitionScanner
            //       向beanFactory中的beanDefinitionMap中添加启动类
            //   listeners.contextLoaded(context);
            //       调用EventPublishingRunListener对象.contexted(context)方法
            //            1.循环监听器,对某监听器实现ApplicationContextAware的接口
            //              某监听器.setApplicationContext(context)
            //            2.initialMulticaster.multicastEvent(ApplicationPreparedEvent)
            //              对一些实现ApplicationListener接口的实现类调用 
            //                           listener.onApplicationEvent(event)
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            
            // this.refresh(context)
            //    context.refresh()->{
            //       ##context.prepareRefresh()->{
            //           1 metadataReaderFactory属性metadataReaderCache清除
            //           2 prepareRefresh()初始化startupDate,closed=false,active=ture
            //                 如果有ServletContex,ServletConfig,初始化PropertySources                                                  
            //       } 
            //       ##beanFactory = this.obtainFreshBeanFactory() 
            //             初始化context.refreshed=ture,deflistBeanFac.serializationId
            //       ##this.prepareBeanFactory(beanFactory)
            //          初始化beanClassLoader=AppClassLoader对象
            //          初始化BeanPostProcessor=ApplicationContextAwareProcessor对象
            //          初始化BeanPostProcessor=ApplicationListenerDetector对象
            //          初始化BeanExpressionResolver=StandardBeanExpressionResolver对象
            //          初始化PropertyEditorRegistrar=ResourceEditorRegistrar对象
            //          初始化ignoreDependencyInterface=EnvironmentAware, 
            //            EmbeddedValueResolverAware,ResourceLoaderAware, 
            //            ApplicationEventPublisherAware,MessageSourceAware,
            //            ApplicationContextAware
            //          初始化registerResolvableDependency=BeanFactory/ResourceLoader等
            //          beanFactory.registerSingleton(environment/systemProperties
            //                        systemEnvironment)
            //       ##context.postProcessBeanFactory(beanFactory)
            //          beanFactory.addBeanPostProcessor(WebApplication
            //                                   ContextServletContextAwareProcessor)
            //          beanFactory.ignoreDependencyInterface(ServletContextAware.class)
            //       ##context.invokeBeanFactoryPostProcessors(beanFactory)
            //          读取配置类,加载对象的beanDefinition到beanDefinitionMap
            //          invokeBeanFactoryPostProcessors有3个
            //            1ConfigurationWarningsApplicationContextInitializer$Conf
            //                                     igurationWarningsPostProcessor
            //            2SharedMetadataReaderFactoryContextInitializer$CachingMe
            //                                      tadataReaderFactoryPostProcessor
            //            3ConfigFileApplicationListener$PropertySource
            //                                       OrderingPostProcessor
            //          regularPostProcessors放入3,
            //          registryProcessors放入1,2,ConfigurationClassPostProcessor后入
            //          processedBeans=internalConfigurationAnnotationProcessor
            //          currentRegistryProcessors=ConfigurationClassPostProcessor
            //          ConfigurationClassPostProcessor.processConfigBeanDefinitions
            //                                            (registry)
            //             向List<BeanDefinitionHolder> configCandidate添加启动类
            //             BeanDefinitionHolder得到启动类的AnnotatedGenericBeanDefinition
            //             new ConfigurationClassParser.parse(configCandidate)
            //             new ConfigurationClassParser.processConfigurationClass(
            //               ConfigurationClass对象为启动类的相关信息)
            //               ConfigurationClassParser.doProcessConfigurationClass(
            //                          参数为启动类元数据)
            //                   processMemberClasses(configClass,sourceClass)
            //                       处理sourceClass的内部类
            //                   处理PropertySources注解,处理ComponentScans注解
            //                   处理Import注解,
            //                       1,递归收集Import的类2,处理
            //                   ,处理ImportResource注解,处理bean注解,处理方法
            //                   处理父类,子类
            //        ##this.registerBeanPostProcessors(beanFactory)
            //          PostProcessorRegistrationDelegate.registerBeanPostProcessors(...)
            //             priorityOrderedPostProcessors=2 AutowiredAnno
            //               tationBeanPostProcessor,3 RequiredAnnotat
            //                          ionBeanPostProcessor             
            //               ,1 CommonAnnotationBeanPostProcessor, 
            //                 0 ConfigurationPropertiesBindingPostProcessor
            //             internalPostProcessors为priorityOrderedPostProcessor后3个
            //             orderedPostProcessor=MethodValidationPostProcessor
            //             nonOrderedPostProcessor=WebServerFactoryCusto
            //               mizerBeanPostProcessor,ErrorPageRegistrarBeanPostProcessor
            //             beanFactory添加priorityOrderedPostProcessors, 
            //               orderedPostProcessor,nonOrderedPostProcessor, 
            //               internalPostProcessors ,new ApplicationListenerDetector(...)    
            //       ##this.initMessageSource()  
            //          用默认context.messageSource=new DelegatingMessageSource()
            //          并向beanFactory注册该单例对象     
            //       ##this.initApplicationEventMulticaster();
            //          用默认context.applicationEventMulticaster=new 
            //                     SimpleApplicationEventMulticaster()                      
            //          并向beanFactory注册该单例对象
            //       ##this.onRefresh();
            //          super.onRefresh()
            //             context.themeSource = new ResourceBundleThemeSource()
            //          context.createWebServer()->this.getWebServerFactory() 这个方法
            //             会创建一些单例对象以及tomcatWebServer,
            //             beanFactory.doGetBean("tomcatServletWebServerFactory",...)
            //                beanFactory.getSingleton(...)
            //                   首先从this.singletonObjects取对象,为null且 
            //                       singletonsCurrentlyInCreation没有,则返回null。
            //                   在this.alreadyCreated没有创建,在mergedBeanDefinitions
            //                        清除object,在alreadyCreated加入object
            //                   根据名字从beanDefinitionMap得到ccbd,加入 
            //                        mergedBeanDefinitions 
            //                   ccbd单例,
            //                   singletonsCurrentlyInCreation加入
            //                   doCreateBean(...)
            //                      createBeanInstance(...)
            //                          instantiateBean() 调用无参构造器
            //                      applyMergedBeanDefinitionPostProcessors
            //                          得到BeanPostProcessors,调用实现 
            //                          MergedBeanDefinitionPostProcessor接口,
            //                          调用postProcessMergedBeanDefinition()方法
            //                      ccbd.postProcessed = true
            //
            //                      populateBean(...)
            //                          InstantiationAwareBeanPostProcessor接口
            //                          postProcessAfterInstantiation
            //                      initializeBean(...)
            //                          invokeAwareMethods()
            //                               BeanNameAware/setBeanName(),
            //                               BeanClassLoaderAware/setBeanClassLoader()
            //                               BeanFactoryAware/setBeanFactory()
            //                          applyBeanPostProcessorsBeforeInitialization()
            //                               postProcessBeforeInitialization()
            //                          invokeInitMethods()
            //                               InitializingBean/afterPropertiesSet()
            //                               invokeCustomInitMethod()
            //                          applyBeanPostProcessorsAfterInitialization()
            //                               postProcessAfterInitialization()            
            //                            
            //
            //                          
            //                          
            //             创建tomcatWebServer,在环境变量里添加servletContext=
            //                ApplicationContextFacade
            //
            //       ##this.registerListeners();
            //          初始化context.applicationEventMulticaster添加监听器及监听器bean
            //       ##this.finishBeanFactoryInitialization(beanFactory);
            //          beanFactory.configurationFrozen = true
            //          beanFactory.frozenBeanDefinitionNames=beanFactory
            //                                                .beanDefinitionNames              
            //          beanFactory.preInstantiateSingletons(); 单例创建                     
            //       ##this.finishRefresh();
            //         context.publishEvent(ContextRefreshedEvent)
            //         context.webServer.start()
            //         context.publishEvent(ServletWebServerInitializedEvent)
            //    }
            // context.registerShutdownHook()
            //   如果SpringApplication对象的registerShutdownHook=ture,则向
            //   context注册shoutdown构造(在new线程中做context.doClose()->{
            //         ##context.publishEvent(ContextClosedEvent)
            //         ##context.destroyBeans()->{
            //           defaultListableBeanFactory.destroySingletons()
            //               1.DefaultSingletonBeanRegistry.destroySingletons()
            //                    得到singletonObjects,
            //                    遍历disposableBeans的beanName    
            //                    De...SingleBeanRegistry.destroySingleton(beanName),
            //                          def..List..BeanFactory.removeSingleton(beanName) 
            //                               singletonObjects.remove(beaname) 
            //                               singletonFactories.remove(beanName)
            //                               earlySingletonObjects.remove(beanName)
            //                               registeredSingletons.remove(beanName)
            //                               factoryBeanObjectCache.remove(beanName)
            //                               factoryBeanInstanceCache.remove(beanName)
            //                             disposableBeans.remove(beanName)
            //                             destroyBean(beanName,DisposableBeans)->{
            //                              dependentBeanMap.remove(beanName)
            //                              destroySingleton(dependentBeanName)
            //                             }
            //                          def..List.manualSingletonNames.remove(beanName);
            //                          def..List.clearByTypeCache();
            //                    containedBeanMap.clear()
            //                    dependentBeanMap()
            //                    dependenciesForBeanMap()
            //                    clearSingletonCache()
            //
            //              2.defaultListableBeanFactory.manualSingletonNames.clear()
            //              3.defaultListableBeanFactory.allBeanNamesByType.clear();
            //                 default...beanFactory.singletonBeanNamesByType.clear();
            //           }
            //         ##context.closeBeanFactory()
            //         ##this.onClose();
            //             context.webServer.stop();TomcatWebServer
            //         ##this.active.set(false)
            //       })
            
            this.afterRefresh(context, applicationArguments);
            //  doNothing
            stopWatch.stop();
            //  统计任务对象stopWatch.TaskInfo,属性taskName="" ,耗时timeMillis
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            //  context.publishEvent(ApplicationStartedEvent) 上下文对象发布应用
            //  启动完成事件
            this.callRunners(context, applicationArguments);
            //  调用ApplicationRunner和CommandLineRunner接口的实现类
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            // context.publishEvent(ApplicationReadyEvent) 发布应用准备完成事件
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
 }


            

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值