SpringIOC容器初始化源码解析(4)—— 手撕Spring容器的刷新逻辑(较硬核(1)


来到prepareBeanFactory(beanFactory)方法,此时已经获取到了DefaultListableBeanFactory的实例:

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {

// Tell the internal bean factory to use the context’s class loader etc.

// 给内置容器设置上外部容器的加载器

beanFactory.setBeanClassLoader(getClassLoader());

// 设置beanFactory的表达式语言处理器,Spring3开始增加了对语言表达式的支持,默认可以使用#{bean.xxx}的形式来调用相关属性值

beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));

// 为beanFactory增加一个默认的propertyEditor

beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

// Configure the bean factory with context callbacks.

// 添加该处理器的作用:当应用程序定义的Bean实现ApplicationContextAware接口时注入ApplicationContext

beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));

// 如果某个bean 依赖于以下几个接口的实现类,在自动装配的时候忽略它们

// Spring 会通过其他方式来处理这些依赖。

beanFactory.ignoreDependencyInterface(EnvironmentAware.class);

beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);

beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);

beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);

beanFactory.ignoreDependencyInterface(MessageSourceAware.class);

beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

// BeanFactory interface not registered as resolvable type in a plain factory.

// MessageSource registered (and found for autowiring) as a bean.

// 修正依赖,这里是注册一些自动装配的特殊规则,比如是BeanFactory class接口的实现类,则在运行时修指定为当前BeanFactory

beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);

beanFactory.registerResolvableDependency(ResourceLoader.class, this);

beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);

beanFactory.registerResolvableDependency(ApplicationContext.class, this);

// Register early post-processor for detecting inner beans as ApplicationListeners.

// 注册早期后置处理器,用于检测内部bean作为应用程序监听器

// ApplicationListenerDetector的作用就是判断某个Bean是否是ApplicationListener,

// 如果是,加入到事件监听者队列。

beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

// Detect a LoadTimeWeaver and prepare for weaving, if found.

// 如果找到一个LoadTimeWeaver,那么就准备将后置处理器“织入"bean工厂

if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {

beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));

// Set a temporary ClassLoader for type matching.

// 为类型匹配设置临时类加载器

beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));

}

// Register default environment beans.

// 注册默认environment环境bean

if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {

beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());

}

if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {

beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());

}

if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {

beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());

}

}

  • 先给内置容器设置上外部容器的加载器,这样就可以让内部容器通过此类加载器去加载外部容器指定的资源了;

  • 第2步主要给内置容器设置上表达式语言处理器;

  • 之后就给内置容器加上一个默认的属性编辑器,用来编辑属性值的;

  • 之后会添加一个ApplicationContextAwareProcessor后置器,该后置器的作用是当应用程序定义的bean出现了ApplicationContextAware接口时,去给这些bean实例注入ApplicationContext对象;

  • 之后对于一些实现了特殊的接口的实现类,先忽略,随后通过其他方式再来处理;

  • 注册一些自动装配的规则,在运行时动态指定这些class对应的实现类;

  • 往容器里设置一个用来检查容器bean里是否有事件监听器的bean的beanPostProcessor;

  • 之后看看是否需要一个LTW织入器,在类加载的时候进行AOP的增强操作,如果有的话,就把该LWT级别的后置处理器添加到容器里;同时设置一个临时类加载器来负责加载并修改bean对应的行为;在AOP的时候会产生很多临时类,把他们放在临时类加载器里会更好;

  • 最后会设置一些默认环境的bean,承接一些默认的系统环境变量。


之后会来到postProcessBeanFactory(beanFactory),该方法允许子类去注册一些postProcessor,是一个钩子方法(子类可选择是否实现)

protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {

}

发现针对于本容器而言,是不需要注册子类的postProcessor的


接下来执行invokeBeanFactoryPostProcessors(beanFactory),进入到相应方法里

请添加图片描述

public static void invokeBeanFactoryPostProcessors(

ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors) {

// Invoke BeanDefinitionRegistryPostProcessors first, if any.

// 如果有BeanDefinitionRegistryPostProcessor的话优先执行

Set processedBeans = new HashSet<>();

// 如果是BeanDefinitionRegistry类型的话

if (beanFactory instanceof BeanDefinitionRegistry) {

BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

// 用于记录常规BeanFactoryPostProcessor

List regularPostProcessors = new ArrayList<>();

// 用于记录BeanDefinitionRegistryPostProcessor

List registryProcessors = new ArrayList<>();

// 遍历所有参数传递进来的 BeanFactoryPostProcessor(它们并没有作为bean注册在容器中)

for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {

if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {

BeanDefinitionRegistryPostProcessor registryProcessor =

(BeanDefinitionRegistryPostProcessor) postProcessor;

registryProcessor.postProcessBeanDefinitionRegistry(registry);

registryProcessors.add(registryProcessor);

}

else {

regularPostProcessors.add(postProcessor);

}

}

// Do not initialize FactoryBeans here: We need to leave all regular beans

// uninitialized to let the bean factory post-processors apply to them!

// Separate between BeanDefinitionRegistryPostProcessors that implement

// PriorityOrdered, Ordered, and the rest.

List currentRegistryProcessors = new ArrayList<>();

// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.

String[] postProcessorNames =

beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);

for (String ppName : postProcessorNames) {

if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {

currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));

processedBeans.add(ppName);

}

}

sortPostProcessors(currentRegistryProcessors, beanFactory);

registryProcessors.addAll(currentRegistryProcessors);

invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);

currentRegistryProcessors.clear();

// Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.

postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);

for (String ppName : postProcessorNames) {

if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {

currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));

processedBeans.add(ppName);

}

}

sortPostProcessors(currentRegistryProcessors, beanFactory);

registryProcessors.addAll(currentRegistryProcessors);

invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);

currentRegistryProcessors.clear();

// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.

boolean reiterate = true;

while (reiterate) {

reiterate = false;

postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);

for (String ppName : postProcessorNames) {

if (!processedBeans.contains(ppName)) {

currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));

processedBeans.add(ppName);

reiterate = true;

}

}

sortPostProcessors(currentRegistryProcessors, beanFactory);

registryProcessors.addAll(currentRegistryProcessors);

invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);

currentRegistryProcessors.clear();

}

// Now, invoke the postProcessBeanFactory callback of all processors handled so far.

// 因为BeanDefinitionRegistryPostProcessor继承自BeanFactoryPostProcessor,所以这里

// 也对所有 BeanDefinitionRegistryPostProcessor 调用其方法 postProcessBeanFactory()

invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);

// 对所有常规 BeanFactoryPostProcessor 调用其方法 postProcessBeanFactory()

invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);

}

else {

// Invoke factory processors registered with the context instance.

invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);

}

// Do not initialize FactoryBeans here: We need to leave all regular beans

// uninitialized to let the bean factory post-processors apply to them!

String[] postProcessorNames =

beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

// Separate between BeanFactoryPostProcessors that implement PriorityOrdered,

// Ordered, and the rest.

List priorityOrderedPostProcessors = new ArrayList<>();

List orderedPostProcessorNames = new ArrayList<>();

List nonOrderedPostProcessorNames = new ArrayList<>();

for (String ppName : postProcessorNames) {

if (processedBeans.contains(ppName)) {

// skip - already processed in first phase above

}

else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {

priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));

}

else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {

orderedPostProcessorNames.add(ppName);

}

else {

nonOrderedPostProcessorNames.add(ppName);

}

}

// First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.

sortPostProcessors(priorityOrderedPostProcessors, beanFactory);

invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);

// Next, invoke the BeanFactoryPostProcessors that implement Ordered.

List orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());

for (String postProcessorName : orderedPostProcessorNames) {

orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));

}

sortPostProcessors(orderedPostProcessors, beanFactory);

invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);

// Finally, invoke all other BeanFactoryPostProcessors.

List nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());

for (String postProcessorName : nonOrderedPostProcessorNames) {

nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));

}

invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);

// Clear cached merged bean definitions since the post-processors might have

// modified the original metadata, e.g. replacing placeholders in values…

beanFactory.clearMetadataCache();

}

将容器或者从参数传入进来的,包括实现了BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor的容器级别的PostProcessor接口的类按照优先级来执行一遍;

由于注解这一块先前obtainFreshBeanFactory方法执行的时候并没有把业务的BeanDefinition实例给定位、解析、注册到容器里,因此这里便调用实现了BeanDefinitionRegistryPostProcessor接口的ConfigurationClassPostProcessor这个后置处理器里面的postProcessBeanDefinitionRegistry方法,最终调用DefaultListableBeanFactory的方法来进行BeanDefinition实例的注册。


完成了容器级别的后置处理器之后就是bean级别的后置处理器了registerBeanPostProcessors(beanFactory),将先前定义的bean级别的后置处理器给注册到容器里,方便之后在调用getBean方法去创建bean实例的时候,在特定的环节去触发这些后置处理器,执行一些特定的逻辑,如AOP织入。


注册完BeanPostProcessor后就到了下一步,initMessageSource()是进行一些国际化的配置,主要是针对不同的地区去展示不同的语言。


接下来就是初始化事件发布器initApplicationEventMulticaster(),用于接受实现了ApplicationEventPublisher接口的类发送过来的不同的事件,并派发给不同的事件监听者来处理。

protected void initApplicationEventMulticaster() {

ConfigurableListableBeanFactory beanFactory = getBeanFactory();

if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {

this.applicationEventMulticaster =

beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);

if (logger.isTraceEnabled()) {

logger.trace(“Using ApplicationEventMulticaster [” + this.applicationEventMulticaster + “]”);

}

}

else {

this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);

beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);

if (logger.isTraceEnabled()) {

logger.trace(“No '” + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "’ bean, using " +

“[” + this.applicationEventMulticaster.getClass().getSimpleName() + “]”);

}

}

}

先判断容器有没有注册过自定义的事件发布器,有的话直接使用,否则就用默认的SimpleApplicationEventMulticaster作为事件发布器,进入到SimpleApplicationEventMulticaster

@Override

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {

ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));

Executor executor = getTaskExecutor();

for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {

if (executor != null) {

// 异步发布事件

executor.execute(() -> invokeListener(listener, event));

}

else {

// 同步发布事件

invokeListener(listener, event);

}

}

}

该事件发布器既支持同步发布事件,又支持异步发布事件去给注册的监听器去做处理。


接下来执行onRefresh()方法

protected void onRefresh() throws BeansException {

// For subclasses: do nothing by default.

}

这是一个钩子方法,主要是预留给AbstractApplicationContext的子类用于初始化其他特殊的bean,该方法会发生在refresh的finishBeanFactoryInitialization(beanFactory);之前,也就是会发生在单例的bean的实例前去执行.

针对这个容器实例而言,并没有相关的onRefresh()方法的实现,onRefresh()方法主要是用在一些web容器上,比如AbstractRefreshableWebApplicationContext的onRefresh()方法:

@Override

protected void onRefresh() {

this.themeSource = UiApplicationContextUtils.initThemeSource(this);

}

在这里会初始化一些和主题相关的bean


之后会执行registerListeners()方法,往先前已经注册出来的ApplicationEventMulticaster(事件发布器)中去注册相应的监听器,用来监听不同的事件,至此,事件、事件发布器、事件监听器就都有了。


接下来来到finishBeanFactoryInitialization(beanFactory)方法里,

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {

// Initialize conversion service for this context.

if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&

beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {

beanFactory.setConversionService(

beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));

}

// Register a default embedded value resolver if no bean post-processor

// (such as a PropertyPlaceholderConfigurer bean) registered any before:

// at this point, primarily for resolution in annotation attribute values.

if (!beanFactory.hasEmbeddedValueResolver()) {

beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));

}

// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.

String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);

for (String weaverAwareName : weaverAwareNames) {

getBean(weaverAwareName);

}

// Stop using the temporary ClassLoader for type matching.

// 停止使用临时类加载器进行类型匹配

beanFactory.setTempClassLoader(null);

// Allow for caching all bean definition metadata, not expecting further changes.

// 允许缓存所有bean定义元数据,不希望有进一步的更改

beanFactory.freezeConfiguration();

// Instantiate all remaining (non-lazy-init) singletons.

beanFactory.preInstantiateSingletons();

}

该方法首先看一下容器里有没有自动的类型转换器CONVERSION_SERVICE_BEAN_NAME,如果有则直接从容器里获取提供类型转换服务的bean实例。

类型转换器就是在配置中给bean实例的属性赋值时需要将属性值转化成对应的类型,只有Spring容器本身不支持的一些类型转换需要这些转换器来处理。

第12行往容器里注册默认的解析器,这个解析器能解析配置文件的值,并将他们注入到@value注解标记,或者xml里面的${}中间的变量里。

再往后就是获取LoadTimeWeaverAware编织器的bean实例以便进行AOP的类加载操作,接下来就是停止使用临时类加载器进行类型匹配,因为在这个地方它已经完成了使命。

beanFactory.freezeConfiguration()使得保存在内存里,先前刷新好的内容变得稳定可靠。

beanFactory.preInstantiateSingletons()实例化所有剩余的(non-lazy-init非延时加载的)单例,高级容器的bean的默认scope就是singleton,非延迟加载。

进入到preInstantiateSingletons里:

public void preInstantiateSingletons() throws BeansException {

if (logger.isTraceEnabled()) {

logger.trace("Pre-instantiating singletons in " + this);

}

// Iterate over a copy to allow for init methods which in turn register new bean definitions.

// While this may not be part of the regular factory bootstrap, it does otherwise work fine.

List beanNames = new ArrayList<>(this.beanDefinitionNames);

// Trigger initialization of all non-lazy singleton beans…

for (String beanName : beanNames) {

RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);

if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {

if (isFactoryBean(beanName)) {

Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);

if (bean instanceof FactoryBean) {

final FactoryBean<?> factory = (FactoryBean<?>) bean;

boolean isEagerInit;

if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {

isEagerInit = AccessController.doPrivileged((PrivilegedAction)

((SmartFactoryBean<?>) factory)::isEagerInit,

getAccessControlContext());

}

else {

isEagerInit = (factory instanceof SmartFactoryBean &&

((SmartFactoryBean<?>) factory).isEagerInit());

}

if (isEagerInit) {

getBean(beanName);

}

}

}

else {

getBean(beanName);

}

}

}

// Trigger post-initialization callback for all applicable beans…

for (String beanName : beanNames) {

Object singletonInstance = getSingleton(beanName);

if (singletonInstance instanceof SmartInitializingSingleton) {

final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;

if (System.getSecurityManager() != null) {

AccessController.doPrivileged((PrivilegedAction) () -> {

smartSingleton.afterSingletonsInstantiated();

return null;

}, getAccessControlContext());

}

else {

smartSingleton.afterSingletonsInstantiated();

}

}

}

}

首先逐个遍历BeanDefinition的名字,然后根据名字去获取到相应的BeanDefinition实例,用RootBeanDefinition去承载合并在一起的BeanDefinition

RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);

getMergedLocalBeanDefinition的主要作用就是兼容各种BeanDefinition,普通的bean在Spring加载bean定义的时候实例化出来的是GenericBeanDefinition,GenericBeanDefinition可直接转化成RootBeanDefinition,对于另外的具有继承关系的ChildBeanDefinition,它的parent会指定另外一个BeanDefinition,RootBeanDefinition 就会将ChildBeanDefinition和parent的属性给合并在一起。

获取到了RootBeanDefinition之后先确保它不是抽象的、是单例的、是非延迟加载的,之后着手于实例化,实例化的时候先看一下是不是FactoryBean,是的话就加上&前缀去从容器里获取FactoryBean的实例,获取到了之后再看下FactoryBean里的bean是否是延迟加载的,如果不是延迟加载,并且相关的创建方法具有权限访问的话就将Factory中的bean实例通过getBean(beanName);创建出来。

if (isEagerInit) {

getBean(beanName);

}

这里涉及到了SmartFactoryBean接口,该接口主要是留给框架内部服务来实现的,能够自动创建出所需要的的对象实例,同时还可以设定实例是延迟/非延迟加载的

default boolean isEagerInit() {

return false;

}

getBean(beanName)执行完之后,就会执行for循环,在循环里先调用getSingleton方法,主要是从容器的缓存里去获取bean的实例防止重复创建,获取到了之后会去看他有没有实现SmartInitializingSingleton接口,该接口作用是批量处理初始化好的Singleton实例。

进入到afterSingletonsInstantiated方法中,是在EventListenerMethodProcessor类里

public void afterSingletonsInstantiated() {

ConfigurableListableBeanFactory beanFactory = this.beanFactory;

Assert.state(this.beanFactory != null, “No ConfigurableListableBeanFactory set”);

String[] beanNames = beanFactory.getBeanNamesForType(Object.class);

最后

各位读者,由于本篇幅度过长,为了避免影响阅读体验,下面我就大概概括了整理了

Init) {

getBean(beanName);

}

这里涉及到了SmartFactoryBean接口,该接口主要是留给框架内部服务来实现的,能够自动创建出所需要的的对象实例,同时还可以设定实例是延迟/非延迟加载的

default boolean isEagerInit() {

return false;

}

getBean(beanName)执行完之后,就会执行for循环,在循环里先调用getSingleton方法,主要是从容器的缓存里去获取bean的实例防止重复创建,获取到了之后会去看他有没有实现SmartInitializingSingleton接口,该接口作用是批量处理初始化好的Singleton实例。

进入到afterSingletonsInstantiated方法中,是在EventListenerMethodProcessor类里

public void afterSingletonsInstantiated() {

ConfigurableListableBeanFactory beanFactory = this.beanFactory;

Assert.state(this.beanFactory != null, “No ConfigurableListableBeanFactory set”);

String[] beanNames = beanFactory.getBeanNamesForType(Object.class);

最后

各位读者,由于本篇幅度过长,为了避免影响阅读体验,下面我就大概概括了整理了

[外链图片转存中…(img-YHnh3ksk-1714469526500)]

[外链图片转存中…(img-ATvWgpFh-1714469526500)]

[外链图片转存中…(img-uQhgHz6R-1714469526500)]

[外链图片转存中…(img-hwKdZL42-1714469526501)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值