spring源码解读-4.把创建的bean丢到容器中

Spring源码解读 Spring Framework源码地址:https://github.com/spring-projects/spring-framework 从目录结构可以看出整个Spring框架都遵从模块化设计的思路,总共分为20多个模块,如下图所示: 从上图可以看出: Spring主要分为五个部分: 1、测试模块:主要实现了Spring的Junit等测试框架。 2、Spring核心容器:里面... 阅读详情

这里呢,说丢到容器中,其实单例bean才有探讨价值;非单例的每次获取是重新进行create的;
大家应该都听过三级缓存。循环依赖这些概念。spring把bean丢在容器中,其实就是和三级缓存进行交互,现在呢就来揭开三级缓存的面纱。以及看看Spring中如何解决循环依赖的;但是今天的主题不探究为什么这样解决哈。我也不建议你现在思考为什么,因为你还不知道他是啥就研究为什么不是自讨苦吃吗?切记别想啊,现在就是去看是什么,而不是为什么!ok?
在这里插入图片描述

下面分几个场景来讲:

场景1:如果简简单单的创建一个bean,没有循环依赖的情况;

先说结论:其实咱们只需要用到一级缓存;逻辑也很简单,就是创建bean,然后创建完成把bean放到一级缓存中;没那么多花里胡哨的;这样就够了。但是其实这里面三级缓存也被放了一个带有bean引用的工厂,但是没用到,创建完成就删除了;这时候在三级缓存的存在就是个没用的废物;

先按照场景1的思路看:
getBean到doGetBean发现没有去createBean然后调用doCreateBean;这个链路都熟悉了不?
去doGetBean里面找到下面这一段;这里就是创建逻辑开始的地方;咱们就从这里开始看:
路标1:ObjectFactory的getObject方法里面调用createBean
这里只是创建了一个ObjectFactory作为getSingleton的参数,并没有真正调用createBean;
下面进去getSingleton方法;

sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
						@Override
						public Object getObject() throws BeansException {
							try {
								return createBean(beanName, mbd, args);
							}
							catch (BeansException ex) {
								// Explicitly remove instance from singleton cache: It might have been put there
								// eagerly by the creation process, to allow for circular reference resolution.
								// Also remove any beans that received a temporary reference to the bean.
								destroySingleton(beanName);
								throw ex;
							}
						}
					});

路标2:singletonFactory.getObject()

1.可以看到这里面调用了上面传入的singletonFactory的getObject方法;然后间接的去调用了createBean,然后拿到创建的bean的值;

2.这里面创建bean的值拿到之后调用了
addSingleton(beanName, singletonObject);
这里面就是创建完成对象之后加入到一级缓存的逻辑。
看路标3咱们进去看看

	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "'beanName' must not be null");
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				if (this.singletonsCurrentlyInDestruction) {
					throw new BeanCreationNotAllowedException(beanName,
							"Singleton bean creation not allowed while the singletons of this factory are in destruction " +
							"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
				}
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<Exception>();
				}
				try {
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
					// Has the singleton object implicitly appeared in the meantime ->
					// if yes, proceed with it since the exception indicates that state.
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						throw ex;
					}
				}
				catch (BeanCreationException ex) {
					if (recordSuppressedExceptions) {
						for (Exception suppressedException : this.suppressedExceptions) {
							ex.addRelatedCause(suppressedException);
						}
					}
					throw ex;
				}
				finally {
					if (recordSuppressedExceptions) {
						this.suppressedExceptions = null;
					}
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return (singletonObject != NULL_OBJECT ? singletonObject : null);
		}
	}

路标3:直接看注释吧

任何单例bean创建成功都会走到这个逻辑;

	protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
			//加入到一级
			this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
			//删除三级
			this.singletonFactories.remove(beanName);
			//删除二级
			this.earlySingletonObjects.remove(beanName);
			//标记该单例已经注册
			this.registeredSingletons.add(beanName);
		}
	}

场景一整体逻辑看完了。但是好像没看到另外两个缓存什么事是不是。确实在场景1的情况下另外两个缓存存在的意义不大。三级缓存存在了一霎那,但是也是个没用到的废物。
那一霎那在哪里呢。其实就是在创建bean的逻辑中;带着疑问看场景2的时候你自然就能看到了。

场景2:如果存在循环依赖的情况下

在这里插入图片描述

也先说结论:这里三个缓存都用到了;过程是这样的

在这里插入图片描述

这里解决循环依赖的关键就是提前把A暴露给B使用了。什么叫提前呢?就是说没初始化完成就给B了。

这里呢大家要先熟记住,Spring在出现循环依赖的时候的整个处理流程。一定要知道他是怎么处理了之后,你才有必要去问他为什么要这样做。不然你都不知道这个东西是什么你就去研究为什么的话,那不就本末倒置了么;没记住的看到这里赶紧翻上去再看一遍;记住了的话咱们继续往下看;

下面咱们先不研究为什么,因为本篇主题是看源码。咱们根据上面的流程去看代码。而不是为什么这样设计。为什么这样设计的思路大家可以看我另外一篇文章 《Spring三级缓存解决循环依赖必要性研讨》。现在先别去啊,先看代码。

下面看场景2源码:
先按照整流程来讲。已经看过的代码我就不打开了。不然篇幅太长读者也累;
下面你凭本事找这几个路子的源码,你也可以debugger来跟;
第1步: 创建A 进入doCreateBean方法,然后里面开始实例化A;

第2步: 实例化A完后后有下面这一段

路标21:addSingletonFactory
场景1结束的疑问到这里就可以解决了!
addSingletonFactory这里呢就是往三级缓存添加一个对象工厂的地方;也就是添加了一个ObjectFactory,但是这里并没有调用这个getEarlyBeanReference哦。只是封装进去而已并没有调用。

	if (earlySingletonExposure) {
			if (logger.isDebugEnabled()) {
				logger.debug("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			addSingletonFactory(beanName, new ObjectFactory<Object>() {
				@Override
				public Object getObject() throws BeansException {
					return getEarlyBeanReference(beanName, mbd, bean);
				}
			});
		}

然后咱们先看看工厂里面getEarlyBeanReference里面做了什么:
你看不懂它做了什么是不。其实没关系,后面文章会讲。这里只要记住就行了。它就是对这个要创建的bean进行代理。然后把代理过的对象进行返回;执行完addSingletonFactory A加到了三级缓存中
路标22:getEarlyBeanReference

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
					if (exposedObject == null) {
						return exposedObject;
					}
				}
			}
		}
		return exposedObject;
	}

第4步:初始化A 你自己找到populateBean(beanName, mbd, instanceWrapper);这里面的逻辑前面你也看过了。就是给A的属性赋值。然后
第5步:从容器获取获取B
代码不贴了。你只要知道这里面会调用getBean去找B就行了;
第6步: 找B发现没找到B。去通过调用doCreateBean创建B
第7,8步: 创建B时候实例化成功,然后同样会addSingletonFactory 把B放三级缓存
第9步: 到初始化B的逻辑发现需要A那么就去找A.
第10步: 调用doGetBean(A)去找A的时候,会执行这里Object sharedInstance = getSingleton(beanName);
好,之前每次路过这里都是返回空,因为之前看到这里都是返回空;现在这次这里面不会返回空了;咱们进去看看;

这里就是从三级缓存取值的逻辑。就是挨个找呗,一级找不到找二级,二级找不到找三级;如果三级找到了,就调用singletonFactory.getObject();这时候就会触发上面的那个getEarlyBeanReference进行代理然后把代理对象放到二级缓存,删除三级缓存;

路标:22

		protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return (singletonObject != NULL_OBJECT ? singletonObject : null);
	}

第11,12,13步:
B拿到A的早期引用,再往后,B创建完成;
完成后:B的创建逻辑会,调用addSingleton(String beanName, Object singletonObject)
移入一级缓存
第14,15,16步:
B完成后,B返回给A,A也完成了。A移入一级缓存addSingleton(String beanName, Object singletonObject) ,
整个流程结束

就这么个逻辑。

好了写到这里整个bean如何丢到容器中的源代码就都走过一遍了。能get到多少就看你自己的了;

Spring源码之getBean(获取 bean)方法(二)解读 前言 接着上篇博客 spring 源码之 getBean 方法解读 ,在博客最后有个疑问:getBean 的时候总是从单例缓存池(指的是一级缓存 singletonObjects)中拿到了,那么productInfoServiceImpl 这个 bean 是在什么时候初始化的,又在什么时候添加进了单例缓存池(指的是一级缓存 singletonObjects)中呢? public class Test_2 { public static void main(String[] args) { 阅读详情

相关推荐

NSFC结题报告下载脚本失效?3步教你手动修复下载按钮问题(2024最新版)

针对NSFC官网域名变更导致结题报告下载脚本失效的问题,本文提供了详细的手动修复指南。通过三步操作,教你如何修改用户脚本的域名匹配规则,恢复下载按钮功能,确保NSFC结题报告下载器能继续高效工作。

vscode6remote的博客 751

spring源码:IOC容器存储和Bean对象的完整加载流程

spring主要通过BeanFactory接口的getBean方法来从IOC容器,即BeanFactory的实现类中获取某个bean对象实例,但是BeanFactory并不直接操作bean对象的存取,而是通过BeanRegistry的接口来处理,类似读写分离的概念。选中最合适的构造函数后,则进行构造函数的属性对象的注入。所以如果该被依赖的bean对象如果也在构造函数中依赖了当前正在创建的bean对象,则该依赖的bean对象就无法创建了,故出现了循环依赖问题,导致程序异常退出。

薄观约取,厚积薄发 659

形位公差标注的常见误区与优化策略

本文深入解析机械设计中形位公差标注的常见误区与优化策略,特别针对NX2406用户提供实用技巧。从标注冗余、基准选择到公差值设定等五大误区入手,结合工程图实例展示如何提升标注精度与效率,帮助工程师避免常见错误,优化设计流程,确保零件功能实现与制造可行性。

info6的博客 245

spring bean通过注解注入ioc容器的源码跟踪

spring bean通过注解注入ioc容器的源码跟踪 @Repository@Service@Controller@Component@bean注解注入ioc容器 ConfigurationClassPostProcessor ==》ConfigurationClassParser ==》ComponentScanAnnotationParser ==》ClassPathBeanDefinitionScanne ==》ClassPathScanningCandidateComponentProvider

m0_68615056的博客 1117

CTFshow-pwn入门-前置基础pwn32-pwn34

CTFshow-pwn入门-前置基础pwn32-pwn34

你们de4月天的博客 1131

Quantum Espresso Hands-On实战训练(四)——PDOS计算与轨道分析

本文详细介绍了Quantum Espresso中PDOS计算与轨道分析的实战方法,重点解析了PDOS在材料电子结构研究中的核心价值,包括原子级分辨率、轨道级解析和自旋极化分析。通过具体案例和操作步骤,帮助读者掌握从输入文件准备到结果解读的全流程,并提供了高级分析技巧和疑难问题解决方案。

weixin_29211309的博客 504

Spring的IoC容器Bean创建过程从理论到源码分析

文章目录 系列文章目录 前言 一、pandas是什么? 二、使用步骤 1.引入库 2.读入数据 总结 前言 我们都知道Spring有以下很多优点: 方便解耦,简化开发。降低了业务对象替换的复杂性,可以让开发人员更关注业务逻辑 方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付 数据库事务问题 ...... 1.那IOC起到了什么作用? IOC容器的主要作用其实就是第一点说的,方便解耦,简化开发。 (1).那它是如何解耦的? 这就.

持续学习,共同进步 1万+

学习十八-UJA107xA简介及移植-0

目录 1、UJA107xA系列手册 1、介绍 2.2.1 BAT引脚功能 2.4.1 V1、V2稳压器 2.5.1 CAN收发器 3.1 Startup Operation 3.2 Normal Operation 5.1 Attached Files 5.2 NXP UJA107xA Example Software 6、main() 1、UJA107xA系列手册 看完手册关于使用还是没有头绪的,只是想SPI发送指令将V2驱动起来,进而驱动起来CAN收...

m0_56344230的博客 1707

你简历不写精通吗,你说怎么把Bean塞到Spring容器?

作者:小傅哥 博客:https://bugstack.cn 沉淀、分享、成长,让自己和他人都能有所收获!???? 一、前言 小傅哥,你是怎么学习的? 有很多初学编程或者码了几年CRUD砖的小伙伴问我,该怎么学编程?感觉什么都不会怎么办?感觉目前的公司没有核心业务学到不东西呀! 其实我可能和很大一部分的粉丝读者都有类似的经历,在传统类似外包的行业待过、从C#语言两年开发再面Java岗、新到互联网职场感觉太多不会的技术栈等等。 但可能最让我在学习编程上受益的就是不断的折腾这些技术: 关于外包:在外包2年时

bugstack虫洞栈 7015

ABAP Open SQL 数据拷贝导致 SAPSQL_DATA_LOSS:从报错机理到稳妥修复

数据类型与长度不合规。一旦在Open SQL的无损赋值检查里被发现,系统宁可中止也不会偷偷替你截断。把变量声明对齐到字典、在需要时明确表达转换/截断意图、写法上避免落入高危的操作数位置,再配合ATC的预防性检查,既能解决你这次的ST22Dump,也能把同类问题消灭在萌芽状态。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 429

揭秘AI人工智能中Stable Diffusion的模型微调技巧

Stable Diffusion作为一种强大的文本到图像生成模型,在艺术创作、设计、娱乐等多个领域展现出了巨大的应用潜力。然而,其默认的模型输出可能无法满足特定用户或特定场景的个性化需求。本文章的目的在于深入揭秘Stable Diffusion的模型微调技巧,让读者能够根据自己的需求对模型进行定制化调整,生成符合特定风格、主题或要求的图像。范围涵盖了从基础概念到实际操作,从理论原理到项目实战的全方位内容,旨在为读者提供一个系统且深入的学习指南。

大模型应用工坊 949

如何将代码移动到Docker容器中

Docker提供了两个很好的选择来将代码移动到映像或容器中: 绑定安装和Dockerfile COPY 指令 。 在本文中,我将解释为什么映像在生产中应始终使用COPY指令,以及为什么在开发中使用绑定安装会更方便。 Dockerfile COPY指令 Dockerfile中的COPY指令用于将文件或目录从主机文件系统复制到映像中。 例如,以下Dockerfile设置一个NodeJS应用程序以...

编程故事的地方 3496

【光子AI / Photon.AI】manus 通用 Agent 的实现核心技术原理与应用

摘要 Manus是一款由国内团队开发的通用AI Agent,其核心技术原理基于多智能体协作架构、云端全能沙箱、规划-执行-反思循环等系统工程方法。与传统的对话式AI不同,Manus能够在独立的Linux虚拟机环境中自主规划并执行复杂任务,通过编写和运行代码来解决问题,具备强大的纠错能力和工具使用能力。 核心应用场景包括深度行业研究、自动化办公、生活助理、辅助编程和跨平台信息整合等。Manus的工程化落地优势体现在沙箱环境、多智能体调度和容错机制等方面,使其能够完成需要多步骤操作和多种工具调用的复杂任务,将A

AI天才研究院 667

谈谈Spring中的对象跟Bean,你知道Spring怎么创建对象的吗?

5w字长文带你彻底看懂Spring是怎么创建对象的!附10几张图,在创作这篇文章的过程中给官方提交了两个issue

做一个认真的程序员 2万+

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

7.IOC容器源码解读—Bean实例创建过程

结合《实例化Bean的过程图》第11步,解读下面代码。/***/// 实例化类型转换服务// 确保BeanFactory持有嵌入值的解析器if (!// 提前实例化LoadTimeWeaverAware beans// 冻结配置// 实例化单例bean@Override// 获得标准名称// 首先从单例Bean中获取else {// 单例bean不直接返回,当前的bean有可能是工厂bean,Object需要从工厂bean中获取else {

RubyWinner的博客 992

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程

从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释

种一棵树最好的时间是十年前,其次是现在 3975

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Spring容器Bean创建以及Bean生命周期

继上一篇文章 Spring加载IOC容器以及装载Bean源码解读,从源码中我们分析了Spring容器的加载过程,Bean的创建。在本篇文章,我们继续分析Bean的创建过程源码以及Bean的生命周期。根据源码总结出Bean的生命周期流程图如下: 上一篇文章,我们跟到了类AbstractBeanFactory中的doGetBean方法,其中含有创建Bean的关键方法createBean(beanNa...

qq_30035133的博客 1790

4.IOC容器源码解读—Bean定义加载过程解读

BeanDefinition,bean定义的接口行为描述。BeanDefinition接口中定义了Bean定义的相关行为,我们从中可看到很多熟悉的项。如对里面的信息项不熟,请查看代码的注释。同时请对比看xml配置的dtd或xsd,在beans.jar中org.springframework.beans.factory.xml 包下:spring-beans.dtd 或 spring-beans.xsd。/***/......Bean定义的信息最终注册到哪里去了呢?

RubyWinner的博客 986

spring源码02-spring容器启动(容器初始化+bean实例化)源码解读【常见面试题】

步骤1)容器初始化;初始化属性源,替换属性占位符 , 保存应用监听器;步骤2) 获取最新BeanFactory;步骤3)初始化BeanFactory;新增资源编辑器注册器-ResourceEditorRegistrar , 新增Bean后置处理器ApplicationContextAwareProcessor,ApplicationListenerDetector;忽略依赖接口, 注册可处理器依赖步骤4)允许子类自行定义BeanFactory的后置处理逻辑;

PacosonSWJTU的博客 1087

Spring容器初始化和bean创建过程

文章目录Spring容器初始化过程(注解)1.this() 初始化bean读取器和扫描器2. register(annotatedClasses)3 refresh()刷新上下文 前言:一直想详细了解下Spring容器初始化和bean创建过程,今天抽空通过翻阅各种资料,对Spring容器初始化和bean的创建有了大概了解。 Spring容器初始化过程(注解) 以AnnotationConfig...

境里婆娑 851

Spring 源码解读第七弹!bean 标签的解析

Spring 源码解读继续。 本文是 Spring 系列第八篇,如果小伙伴们还没阅读过本系列前面的文章,建议先看看,这有助于更好的理解本文。 Spring 源码解读计划 Spring 源码第一篇开整!配置文件是怎么加载的? Spring 源码第二弹!XML 文件解析流程 Spring 源码第三弹!EntityResolver 是个什么鬼? Spring 源码第四弹!深入理解 BeanDefinition 手把手教你搭建 Spring 源码分析环境 Spring 源码第六弹!松哥和大家聊聊容器的始祖 Def

江南一点雨的专栏 5188

Spring 容器原始 Bean 是如何创建的?

以下内容基于 Spring6.0.4。这个话题其实非常庞大,我本来想从 getBean 方法讲起,但一想这样讲完估计很多小伙伴就懵了,所以我们还是一步一步来,今天我主要是想和小伙伴们讲讲 Spring 容器创建 Bean 最最核心的 createBeanInstance 方法,这个方法专门用来创建一个原始 Bean 实例。松哥这里就以 Spring 源码中方法的执行顺序为例来和小伙伴们分享。

江南一点雨的专栏 1054

Spring IOC 容器源码解读

name: spring-ioc title: Spring IOC 容器源码分析 date: 2020-05-22 16:25:30 tags: categories: open-source Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器。既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢?阅读本文并不能让你成为 Spring 专家,不过一定有助于大家理解 Spring 的很多概念,帮助大家排查应用中和 Sp.

孤刺 1248

spring容器及bean加载机制源码解读

spring的容器加载机制分为提前加载和运行时动态加载,本文通过源码解读容器加载机制和bean加载机制。

淡淡吹风的专栏 382

Spring深度学习:IOC容器及源码解析

大家经常戏称Java工程师为Spring工程师,毫无疑问,这句话体现出Spring框架在Java开发中的重要性和普及度。本文小豪将带大家深度学习Spring IOC底层源码,学习过程中不仅仅是了解Spring IOC的工作原理,更重要的是学会理解框架的底层逻辑,从而提升我们的思维能力。

Code豪客 1676

Spring的IOC源码解读

如:AbstractXmlApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory beanFactory)。注册过程:DefaultListableBeanFactory.registerBeanDefinition()。子类:ClassPathResource(类路径资源)、FileSystemResource(文件系统资源)。:实现了应用程序上下文对象(DI容器)的初始化。:描述与管理Bean定义信息。定义:用于加载资源。

tongwenss的博客 958
上一篇: spring源码解读-3.初始化维护依赖关系
下一篇: spring源码解读-5.容器生命周期之修改图纸
掀衣怒码
博客等级 码龄6年 6粉丝 19原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值