SpringBoot(1.5.6.RELEASE)源码解析(四)

请尊重作者劳动成果,转载请标明原文链接:http://www.cnblogs.com/dylan-java/p/7468336.html

上一篇分析了SpringApplication的run方法的一部分,接下来继续分析剩下的部分

 1 public ConfigurableApplicationContext run(String... args) {
 2     StopWatch stopWatch = new StopWatch();
 3     stopWatch.start();
 4     ConfigurableApplicationContext context = null;
 5     FailureAnalyzers analyzers = null;
 6     configureHeadlessProperty();
 7     SpringApplicationRunListeners listeners = getRunListeners(args);
 8     listeners.starting();
 9     try {
10         ApplicationArguments applicationArguments = new DefaultApplicationArguments(
11                 args);
12         ConfigurableEnvironment environment = prepareEnvironment(listeners,
13                 applicationArguments);
14         Banner printedBanner = printBanner(environment);
15         context = createApplicationContext();
16         analyzers = new FailureAnalyzers(context);
17         prepareContext(context, environment, listeners, applicationArguments,
18                 printedBanner);
19         refreshContext(context);
20         afterRefresh(context, applicationArguments);
21         listeners.finished(context, null);
22         stopWatch.stop();
23         if (this.logStartupInfo) {
24             new StartupInfoLogger(this.mainApplicationClass)
25                     .logStarted(getApplicationLog(), stopWatch);
26         }
27         return context;
28     }
29     catch (Throwable ex) {
30         handleRunFailure(context, listeners, analyzers, ex);
31         throw new IllegalStateException(ex);
32     }
33 }

第19行调用refreshContext方法,refreshContext方法又调用了refresh方法,而refresh方法又调用了context父类AbstractApplicationContext(真正的实现类是EmbeddedWebApplicationContext)的refresh方法,接着又调用AbstractApplicationContext的refresh方法

AbstractApplicationContext的refresh方法是本篇分析的重点,假如你通过ApplicationContext apc = new ClassPathXmlApplicationContext("beans.xml");的方式启动Spring,也是会调用这个refresh方法来初始化容器的,它主要是完成配置类的解析,各种BeanFactoryPostProcessor和BeanPostProcessor的注册,内置容器(tomcat)构造,国际化配置初始化,实例化非延迟加载的单例bean等

 1 @Override
 2 public void refresh() throws BeansException, IllegalStateException {
 3     synchronized (this.startupShutdownMonitor) {
 4         // Prepare this context for refreshing.
 5         prepareRefresh();
 6 
 7         // Tell the subclass to refresh the internal bean factory.
 8         ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 9 
10         // Prepare the bean factory for use in this context.
11         prepareBeanFactory(beanFactory);
12 
13         try {
14             // Allows post-processing of the bean factory in context subclasses.
15             postProcessBeanFactory(beanFactory);
16 
17             // Invoke factory processors registered as beans in the context.
18             invokeBeanFactoryPostProcessors(beanFactory);
19 
20             // Register bean processors that intercept bean creation.
21             registerBeanPostProcessors(beanFactory);
22 
23             // Initialize message source for this context.
24             initMessageSource();
25 
26             // Initialize event multicaster for this context.
27             initApplicationEventMulticaster();
28 
29             // Initialize other special beans in specific context subclasses.
30             onRefresh();
31 
32             // Check for listener beans and register them.
33             registerListeners();
34 
35             // Instantiate all remaining (non-lazy-init) singletons.
36             finishBeanFactoryInitialization(beanFactory);
37 
38             // Last step: publish corresponding event.
39             finishRefresh();
40         }
41 
42         catch (BeansException ex) {
43             if (logger.isWarnEnabled()) {
44                 logger.warn("Exception encountered during context initialization - " +
45                         "cancelling refresh attempt: " + ex);
46             }
47 
48             // Destroy already created singletons to avoid dangling resources.
49             destroyBeans();
50 
51             // Reset 'active' flag.
52             cancelRefresh(ex);
53 
54             // Propagate exception to caller.
55             throw ex;
56         }
57 
58         finally {
59             // Reset common introspection caches in Spring's core, since we
60             // might not ever need metadata for singleton beans anymore...
61             resetCommonCaches();
62         }
63     }
64 }

第3行使用synchronized关键字拿到startupShutdownMonitor的锁,close方法的第一行也是先获取该锁,说明启动容器和关闭容器都是同步操作的,是线程安全的

第5行调用prepareRefresh方法,该方法主要是做些准备工作,例如清空缓存,记录容器启动的时间,设置closed为false,设置active为true,初始化属性信息,验证必要的属性

这里的初始化属性信息和验证必要的属性方法默认是不做任何事情的,但有时候系统运行需要读取某些系统属性,没有这些属性,系统将运行不正常,这个时候,在创建容器之前就要对这些必须的属性做个验证,比如自己实现一个ApplicationContext并覆盖initPropertySources方法

1 public class MyApplicationContext extends AnnotationConfigEmbeddedWebApplicationContext {
2     protected void initPropertySources() {
3         getEnvironment().setRequiredProperties("dylan.required");
4     }
5 }

然后SpringApplication启动的方式也改下

1 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
2 public class Application {
3     public static void main(String[] args) {
4         SpringApplication springApplication = new SpringApplication(new Object[] {Application.class});
5         springApplication.setApplicationContextClass(MyApplicationContext.class);
6         springApplication.run(args);
7     }
8 }

那么当运行到initPropertySources();这个方法时,会调用自定义的ApplicationContext的initPropertySources方法,向environment中添加必要的属性,而运行getEnvironment().validateRequiredProperties();这个方法的时候,就会检查是否配置了"dylan.required"属性,如果没有配置,那么会抛出异常,容器启动失败

第8行通过obtainFreshBeanFactory方法获取到beanFactory

1 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
2     refreshBeanFactory();
3     ConfigurableListableBeanFactory beanFactory = getBeanFactory();
4     if (logger.isDebugEnabled()) {
5         logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
6     }
7     return beanFactory;
8 }

refreshBeanFactory方法把refreshed属性设置成true,表示已经刷新了,下次再刷新就会抛出异常,不允许重复刷新,然后给beanFactory设置serializationId,就是之前通过ContextIdApplicationContextInitializer生成的id

getBeanFactory方法拿到ApplicationContext的beanFactory(之前创建上下文的时候,也就是调用createApplicationContext方法,GenericApplicationContext的构造函数里会实例化一个DefaultListableBeanFactory对象),然后返回

1 public GenericApplicationContext() {
2     this.beanFactory = new DefaultListableBeanFactory();
3 }

第11行调用prepareBeanFactory方法,该方法对beanFactory进行一系列的设置

 1 protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
 2     // Tell the internal bean factory to use the context's class loader etc.
 3     beanFactory.setBeanClassLoader(getClassLoader());
 4     beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
 5     beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
 6 
 7     // Configure the bean factory with context callbacks.
 8     beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
 9     beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
10     beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
11     beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
12     beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
13     beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
14     beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
15 
16     // BeanFactory interface not registered as resolvable type in a plain factory.
17     // MessageSource registered (and found for autowiring) as a bean.
18     beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
19     beanFactory.registerResolvableDependency(ResourceLoader.class, this);
20     beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
21     beanFactory.registerResolvableDependency(ApplicationContext.class, this);
22 
23     // Register early post-processor for detecting inner beans as ApplicationListeners.
24     beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
25 
26     // Detect a LoadTimeWeaver and prepare for weaving, if found.
27     if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
28         beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
29         // Set a temporary ClassLoader for type matching.
30         beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
31     }
32 
33     // Register default environment beans.
34     if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
35         beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
36     }
37     if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
38         beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
39     }
40     if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
41         beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
42     }
43 }

首先设置用于加载bean的类加载器,再设置可以解析bean表达式的表达式解析器(使用"#{xxx}"配置的属性),然后添加属性注册器,并添加了一个BeanPostProcessor - ApplicationContextAwareProcessor的实例

接着设置了6个忽略自动注入的接口(EnvironmentAware、EmbeddedValueResolverAware、ResourceLoaderAware、ApplicationEventPublisherAware、MessageSourceAware、ApplicationContextAware),为什么要忽略呢?因为上一步添加的BeanPostProcessor - ApplicationContextAwareProcessor,BeanPostProcessor会在会在bean初始化前后调用相应的方法,我们看一下ApplicationContextAwareProcessor的代码

 1 @Override
 2 public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
 3     AccessControlContext acc = null;
 4 
 5     if (System.getSecurityManager() != null &&
 6             (bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
 7                     bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
 8                     bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
 9         acc = this.applicationContext.getBeanFactory().getAccessControlContext();
10     }
11 
12     if (acc != null) {
13         AccessController.doPrivileged(new PrivilegedAction<Object>() {
14             @Override
15             public Object run() {
16                 invokeAwareInterfaces(bean);
17                 return null;
18             }
19         }, acc);
20     }
21     else {
22         invokeAwareInterfaces(bean);
23     }
24 
25     return bean;
26 }
27 
28 private void invokeAwareInterfaces(Object bean) {
29     if (bean instanceof Aware) {
30         if (bean instanceof EnvironmentAware) {
31             ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
32         }
33         if (bean instanceof EmbeddedValueResolverAware) {
34             ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
35         }
36         if (bean instanceof ResourceLoaderAware) {
37             ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
38         }
39         if (bean instanceof ApplicationEventPublisherAware) {
40             ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
41         }
42         if (bean instanceof MessageSourceAware) {
43             ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
44         }
45         if (bean instanceof ApplicationContextAware) {
46             ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
47         }
48     }
49 }

从上面红色标注的代码可以看出,在ApplicationContextAwareProcessor的postProcessBeforeInitialization方法里刚好帮我们做了这6个被忽略的Aware接口需要感知的动作

接下来的4行设置了几个自动装配的特殊规则,如果注入的是BeanFactory类型,则注入beanFactory对象,如果是ResourceLoader、ApplicationEventPublisher、ApplicationContext类型,则注入当前对象(Spring上下文 - context)

然后又注册了一个BeanPostProcessor - ApplicationListenerDetector,我们看下ApplicationListenerDetector的代码

1 @Override
2 public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
3     if (this.applicationContext != null) {
4         this.singletonNames.put(beanName, beanDefinition.isSingleton());
5     }
6 }

postProcessMergedBeanDefinition方法在什么时候调用的呢?DEBUG跟进代码可知,是doCreateBean -> applyMergedBeanDefinitionPostProcessors -> postProcessMergedBeanDefinition,该方法会往singletonNames属性添加以beanName为key、该bean定义是否为单例为value的键值对

 1 @Override
 2 public Object postProcessAfterInitialization(Object bean, String beanName) {
 3     if (this.applicationContext != null && bean instanceof ApplicationListener) {
 4         // potentially not detected as a listener by getBeanNamesForType retrieval
 5         Boolean flag = this.singletonNames.get(beanName);
 6         if (Boolean.TRUE.equals(flag)) {
 7             // singleton bean (top-level or inner): register on the fly
 8             this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
 9         }
10         else if (Boolean.FALSE.equals(flag)) {
11             if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
12                 // inner bean with other scope - can't reliably process events
13                 logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +
14                         "but is not reachable for event multicasting by its containing ApplicationContext " +
15                         "because it does not have singleton scope. Only top-level listener beans are allowed " +
16                         "to be of non-singleton scope.");
17             }
18             this.singletonNames.remove(beanName);
19         }
20     }
21     return bean;
22 }

该BeanPostProcessor在bean初始化后,会检查该bean是否是ApplicationListener,如果是,那么会验证它是否是单例,如果不是单例,那么删除singletonNames中对应的key,这也正如它的名字一样 - ApplicationListener探测器

接着往下看

如果beanFactory中有名字是loadTimeWeaver的bean,那么注册一个BeanPostProcessor - LoadTimeWeaverAwareProcessor

LoadTimeWeaverAwareProcessor的代码

 1 @Override
 2 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
 3     if (bean instanceof LoadTimeWeaverAware) {
 4         LoadTimeWeaver ltw = this.loadTimeWeaver;
 5         if (ltw == null) {
 6             Assert.state(this.beanFactory != null,
 7                     "BeanFactory required if no LoadTimeWeaver explicitly specified");
 8             ltw = this.beanFactory.getBean(
 9                     ConfigurableApplicationContext.LOAD_TIME_WEAVER_BEAN_NAME, LoadTimeWeaver.class);
10         }
11         ((LoadTimeWeaverAware) bean).setLoadTimeWeaver(ltw);
12     }
13     return bean;
14 }

该BeanPostProcessor只对LoadTimeWeaverAware类型的bean做特殊处理,查了下Spring中实现LoadTimeWeaverAware接口的类,只有AspectJWeavingEnabler,在Spring中调用AspectJWeavingEnabler时,this.loadTimeWeaver尚未被初始化,那么会直接调用this.beanFactory.getBean方法获取对应的DefaultContextLoadTimeWeaver类型的bean,并设置到AspectJWeavingEnabler类型bean的loadTimeWeaver属性中。当容器中注册了loadTimeWeaver之后会给容器设置一个ContextTypeMatchClassLoader类型的临时类加载器,在织入切面时只有在bean实例化时织入切面才有意义,在进行一些类型比较或者校验的时候,比如判断一个bean是否是FactoryBean、BPP、BFPP,这时候不涉及到实例化,所以做字节码转换没有任何意义,而且还会增加无谓的性能消耗,所以在进行这些类型比较时使用这个临时的类加载器执行类加载。这个临时的类加载器会在容器初始化快结束时,容器bean实例化之前被清掉,代码在AbstractApplicationContext类的finishBeanFactoryInitialization方法

 1 protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
 2     ...
 3 
 4     // Stop using the temporary ClassLoader for type matching.
 5     beanFactory.setTempClassLoader(null);
 6 
 7     // Allow for caching all bean definition metadata, not expecting further changes.
 8     beanFactory.freezeConfiguration();
 9 
10     // Instantiate all remaining (non-lazy-init) singletons.
11     beanFactory.preInstantiateSingletons();
12 }

设置完loadTimeWeaver后,再判断当前beanFactory是否含有名字为environment的bean,如果没有,那么把当前的environment注册进去

接着判断当前beanFactory是否含有名字为systemProperties的bean,如果没有,那么把System.getProperties()注册进去

最后判断当前beanFactory是否含有名字为systemEnvironment的bean,如果没有,那么把System.getenv()注册进去

至此refresh的prepareBeanFactory方法结束,再往下看refresh方法

第15行调用postProcessBeanFactory方法,该方法最终会调用到EmbeddedWebApplicationContext的postProcessBeanFactory方法,给beanFactory添加一个WebApplicationContextServletContextAwareProcessor类型的BeanPostProcessor,并添加了一个忽略装配的接口ServletContextAware,原理跟之前prepareBeanFactory方法分析的一样,WebApplicationContextServletContextAwareProcessor继承了ServletContextAwareProcessor,而ServletContextAwareProcessor的postProcessBeforeInitialization方法代码已经做了ServletContextAware该做的事情

 1 @Override
 2 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
 3     if (getServletContext() != null && bean instanceof ServletContextAware) {
 4         ((ServletContextAware) bean).setServletContext(getServletContext());
 5     }
 6     if (getServletConfig() != null && bean instanceof ServletConfigAware) {
 7         ((ServletConfigAware) bean).setServletConfig(getServletConfig());
 8     }
 9     return bean;
10 }

检查basePackages属性,如果设置了会使用ClassPathBeanDefinitionScanner扫描basePackages并注册被扫描到的bean

检查annotatedClasses属性,如果设置了会使用AnnotatedBeanDefinitionReader注册这些类型的bean

第18行调用invokeBeanFactoryPostProcessors方法,该方法主要是执行bean工厂的后置处理

首先,获取上下的beanFactoryPostProcessors,笔者在DEBUG的时候,发现此时有3个

这3个BeanFactoryPostProcessor是什么时候加进去的呢,前两个其实就是之前分析过的SpringApplication的run方法中调用prepareContext设置上下文的时候,其中有一步applyInitializers,在调用ConfigurationWarningsApplicationContextInitializer和SharedMetadataReaderFactoryContextInitializer的initialize方法中添加进去的,至于第3个,则是在listeners.contextLoaded(context);方法中被添加的,contextLoaded会发布ApplicationPreparedEvent事件,刚好被ConfigFileApplicationListener监听到,而ConfigFileApplicationListener的onApplicationEvent的方法中又会调用onApplicationPreparedEvent方法,没错,就是这个方法添加了PropertySourceOrderingPostProcessor

回归正题,获取到上下文的beanFactoryPostProcessors后,调用了PostProcessorRegistrationDelegate的静态方法invokeBeanFactoryPostProcessors,下面来详细分析一下这个方法

首先判断beanFactory是不是BeanDefinitionRegistry类型的,如果beanFactory是BeanDefinitionRegistry类型的实现类,那么

  - 第一步,遍历上下文获取到的beanFactoryPostProcessors,如果是BeanDefinitionRegistryPostProcessor类型的,那么调用该postProcessor的postProcessBeanDefinitionRegistry方法

  - 第二步,在beanFactory中查询BeanDefinitionRegistryPostProcessor类型的beanName数组,如果是实现了PriorityOrdered接口,那么获取bean并添加到priorityOrderedPostProcessors集合中,对其排序,并调用它的postProcessBeanDefinitionRegistry方法

  - 第三步,在beanFactory中查询BeanDefinitionRegistryPostProcessor类型的beanName数组,如果之前没有处理过该bean(即没有实现PriorityOrdered接口)并实现了Ordered接口,那么获取bean并添加到orderedPostProcessors集合中,对其排序,并调用它的postProcessBeanDefinitionRegistry方法

  - 第四步,在beanFactory中查询BeanDefinitionRegistryPostProcessor类型的beanName数组,如果之前没有处理过该bean(即没有实现PriorityOrdered和Ordered接口),那么获取bean,并调用它的postProcessBeanDefinitionRegistry方法

  - 第五步,对上面调用了postProcessBeanDefinitionRegistry的后置处理器,调用postProcessBeanFactory方法

  - 第六步,对在第一步中讲到的遍历beanFactoryPostProcessors后,不是BeanDefinitionRegistryPostProcessor类型的,那么调用它们的postProcessBeanFactory方法

如果beanFactory不是BeanDefinitionRegistry类型的实现类,那么直接遍历上下文获取到的beanFactoryPostProcessors,调用它们的postProcessBeanFactory方法

接下来,处理BeanFactoryPostProcessor,在beanFactory中查询BeanFactoryPostProcessor类型的beanName数组,遍历该数组,如果之前处理过,直接跳过,否则,获取实现了PriorityOrdered接口的bean,排序并调用它们的postProcessBeanFactory方法,再获取实现了Ordered接口的bean,排序并调用它们的postProcessBeanFactory方法,最后,获取没有实现PriorityOrdered和Ordered接口的bean,调用它们的postProcessBeanFactory方法

方法的最后,清空了beanFactory的缓存

总结一下该方法,其实就是按照一定的顺序,执行BeanFactoryPostProcessor接口的postProcessBeanFactory方法,BeanDefinitionRegistryPostProcessor接口继承了BeanFactoryPostProcessor接口,并定义了postProcessBeanDefinitionRegistry方法

顺序总结如下

1、上下文中beanFactoryPostProcessors并实现了BeanDefinitionRegistryPostProcessor接口 -> postProcessBeanDefinitionRegistry方法

2、beanFactory中实现了PriorityOrdered接口的BeanDefinitionRegistryPostProcessor -> postProcessBeanDefinitionRegistry方法

3、beanFactory中实现了Ordered接口的BeanDefinitionRegistryPostProcessor -> postProcessBeanDefinitionRegistry方法

4、beanFactory中没有实现PriorityOrdered和Ordered接口的BeanDefinitionRegistryPostProcessor -> postProcessBeanDefinitionRegistry方法

5、beanFactory中实现了PriorityOrdered接口的BeanDefinitionRegistryPostProcessor -> postProcessBeanFactory方法

6、beanFactory中实现了Ordered接口的BeanDefinitionRegistryPostProcessor -> postProcessBeanFactory方法

7、beanFactory中没有实现PriorityOrdered和Ordered接口的BeanDefinitionRegistryPostProcessor -> postProcessBeanFactory方法

8、上下文中beanFactoryPostProcessors没有实现BeanDefinitionRegistryPostProcessor接口 -> postProcessBeanFactory方法

9、beanFactory中实现了PriorityOrdered接口的BeanFactoryPostProcessor -> postProcessBeanFactory方法

10、beanFactory中实现了Ordered接口的BeanFactoryPostProcessor -> postProcessBeanFactory方法

11、beanFactory中没有实现PriorityOrdered和Ordered接口的BeanFactoryPostProcessor -> postProcessBeanFactory方法

可以看出,BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法要先于BeanFactoryPostProcessor接口的postProcessBeanFactory方法,它们都可以在Spring容器加载了bean的定义之后,在bean实例化之前,对bean的元数据进行修改

第21行调用registerBeanPostProcessors方法,该方法主要是注册bean的后置处理器

调用PostProcessorRegistrationDelegate的静态方法registerBeanPostProcessors

 

Hello World

转载于:https://www.cnblogs.com/dylan-java/p/7468336.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值