spring源码:循环依赖源码学习

【Spring源码:循环依赖】一文弄懂Spring循环依赖 1. 什么是循坏依赖 很简单,其实就是互相依赖对方,比如,有一个A对象依赖了B对象,B对象又依赖了A对象。 // A依赖了B public class A{ private B b; } // B依赖了A public class B{ private A a; } 但是,在我们普通的java开发中,循坏依赖会出现问题吗? 如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖注入是很正常的事情。 比如: 这样,A,B就互相依赖上了。 但是,在Spring 阅读详情

本文目的

spring默认支持单实例的循环引用,简单而言,就是A类中注入了B类,B类中注入了A类,循环依赖的根本问题是就是属性注入;本文主要记录spring支持循环引用的源码学习;即:从源码层面解读,spring是如何支持循环引用

结论

先文本描述一下,spring是如何支持循环依赖的

  1. 我们以A.class和B.class为例
  2. 在spring实例化A的时候,会经过一些列的方法处理,在其中一步:是对A类进行属性注入,在属性注入的时候,发现A类中注入了B类,所以会调用doGetBean()方法,获取B;
  3. 此时,B由于还没有实例化,所以会对B进行实例化,在执行到B的属性注入的时候,会发现,B类中,需要注入A类,所以会从单实例池中尝试获取A;此时,A还没有注入到单实例池中
  4. 但是,在第二步实例化A的时候,会把A类对应的一个半成品(这里说的半成品我们姑且认为就是一个对象,后面解释),放入到二级缓存中;所以,B类在注入A类的时候,实际而言,注入的是这里说的半成品(未执行初始化方法回调);
  5. 此时,B类会完成注入,并完成后续的AOP代理,并注入到单实例池中
  6. 然后A类会接着第二步,注入B,然后再执行后面的初始化方法和AOP动态代理的处理;最后注入到单实例池中,完成了循环依赖

代码

@Component
public class AddressVO {

    @Autowired
    private UserVO userVO;
}


@Component
public class UserVO {

    @Autowired
    private AddressVO addressVO;

    public void test(){
        System.out.println(addressVO.getClass());
    }
}

@Configuration
@ComponentScan("com.springsource.study.reference")
public class AppConfig {
}

public class TestReference {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        ac.getBean(BBean.class).test();
    }
}

源码学习

1、首先说,循环依赖的处理是在refresh()方法中的finishBeanFactoryInitialization(beanFactory);这个方法中完成的;这个方法的作用是:实例化所有的bean,可以这样理解:在spring执行到这一行代码的时候,所有需要注入到spring中的class类,都已经转换成了beanDefinition,并存入到了beanDefinitionMap中;spring在对要注入的类实例化的时候,直接从beanDefinitionMap中根据beanDefinitionName获取数据;具体是在哪一步将所有的class转换为beanDefinition呢?在invokeBeanFactoryPostProcessors(beanFactory);方法中

2、我们先提前列出,spring在实例化的时候,需要用到的几个关键集合和标志位

//在实例化bean的时候,将当前正在被创建的bean加入到singletonsCurrentlyInCreation,这个操作是在beforeSingletonCreation()方法中完成的
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
  
//这是spring单实例池(也就是我们常说的spring容器)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);

//我们姑且称为spring的二级缓存,addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));在该方法中,会将bean包装为ObjectFactory,存放到map中;这是在spring的一个后置处理器中完成的
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);

//暂时称为三级缓存 三级缓存在循坏依赖中,用来防止重复创建
private final Map<String, Object> earlySingletonObjects = new HashMap(16);

allowCircularReferences:默认值为true,表示允许循环引用

关于上面这四个集合,我会在下面说到的时候,一一标明;
这里有一点要说明的是:
我们常说的spring容器:从狭义上来理解,就是指signletonObjects这个map;但是,详细点来讲,应该是由beanDefinition+beanDefinitionMaps+beanFactory+beanFactoryPostprocessor+singletonObjects…等一些列组件的集合组成的,当然,这里仁者见仁智者见智,我是这么理解的

3、我们下面要说的源码都在finishBeanFactoryInitialization()这个方法中;
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
这是spring在实例化bean的时候,首先进入的方法(前面的校验以及mergeBean,就跳过了,不是本篇文章的重点)
在doGetBean()方法中,我们要说的第一个方法是:
Object sharedInstance = getSingleton(beanName);

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			////这里只需要从二级缓存中拿一次就行,如果没有二级缓存,每次进来都需要从二级缓存get一次,影响效率
			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;
}

在第一次初始化AddressVO,走到这个方法的时候,这里返回的肯定是null,因为在第一次创建的时候,单实例池、二级缓存、三级缓存中都是null;这里先这样理解,在后面还会介绍这个方法;在这里返回null之后,spring就会继续往下进行创建初始化的流程;

4、在初始化bean的时候,没有从单实例池、二级缓存、三级缓存中获取到bean,就会继续执行后面的初始化逻辑,在doGetBean方法中,另一个要说的代码是:

if (mbd.isSingleton()) {
	sharedInstance = getSingleton(beanName, () -> {
		try {
			return createBean(beanName, mbd, args);
		}
		catch (BeansException ex) {
			destroySingleton(beanName);
			throw ex;
		}
	});
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

这个方法是开始执行spring生命周期中后置处理器的入口,其中,在getSingleton方法中,有一个beforeSingletonCreation(beanName);的调用,在这里,将当前bean添加到了singletonsCurrentlyInCreation;然后spring开始实例化bean对象;

5、在实例化bean对象的过程中,会调用到spring中第四个后置处理器

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isDebugEnabled()) {
				logger.debug("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			/**
			 * mpy 第四次调用后置处理器 获取一个提前暴露的对象 objectFactory 用来解决循环依赖
			 * 这里还有一个关键的作用,可以追进去看一下,这里可能会完成动态代理对象的生成(AOP)
			 * 正常情况下,AOP的动态代理是在调用org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String)的时候,才会生成
			 * 但是,如果是循环依赖的话,会在 org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(java.lang.Object, java.lang.String) 中完成
			 * 需要注意:getEarlyBeanReference()是在doGetBean中,从二级缓存中获取半成品对象的时候,才会执行:singletonFactory.getObject();
			 */
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

这里则是:将我们上面所说的半成品的AddressVO添加到二级缓存中的代码逻辑;这里的getEarlyBeanReference()实际是调用了后置处理器,如果当前bean需要被生成动态代理(AOP),就会在这一步提前生成代理对象(这里需要说明的是:正常的逻辑中,执行完这行代码之后,会进行属性注入、再执行bean的初始化方法回调、然后再执行aop动态代理的生成;但是如果是允许循环依赖的话,这里会提前调用AOP的动态代理,将生成的代理对象添加到二级缓存)

6、在添加到二级缓存之后,就会执行下一步,属性注入;在属性注入的时候,会发现,需要注入UserVO;会再次调用到第三步中的doGetBean()方法,这时,由于UserVO是第一次初始化,所以,会执行一般上面所说的第四步、第五步的代码;

7、关键步骤就是在UserVO属性注入的时候,发现需要注入AddresVO,这时会调用doGetBean();方法尝试获取AddressVO对象;这时,和第一次调用doGetBean()获取addressVO对象不同的一点是:现在的二级缓存中,有addressVO的半成品对象;所以,这时候,UserVO会注入从二级缓存中取得的AddressVO对象,userVO继续完成后面的初始化代码;初始化完毕之后,AddressVO再注入UserVO

半成品的bean:
我们上面说到的半成品的bean:我的理解是这样的;在执行完addSingletonFactory代码,到最后完成bean的实例化,中间还有三个步骤,分别是:属性注入、初始化方法回调和aop动态代理的生成;其中循环依赖,就是在属性注入这个操作中完成的,所以,就只有两步,初始化方法回调和aop的动态代理
所以在循环依赖,提前注入bean的时候,需要确保bean是一个有效的,这里的意思是:如果A依赖B,B依赖了A;在往B中注入A的时候,必须确保A是一个接近完整的bean;举例:A在被注入的时候,A还阻塞在属性注入的这行代码;没有执行初始化方法回调,其实影响不大,因为在初始化方法回调中,基本是做一些业务代码的预处理;但是如果B在被注入到A类中的时候,还没有生成代理对象,那这样注入有什么意义呢?所以:spring的操作是:将动态代理的生成这一步,提前到了添加到二级缓存这一步,也就是说:如果是循环依赖的话,是先生成AOP动态代理,再进行属性注入,再进行初始化方法回调;初始化方法回调之后,AOP动态代理的处理方法中,有一个判断:如果当前需要生成代理对象的bean,已经生成了代理对象,就不会再处理

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
	if (bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		/**
		 * 下面这行代码的意思是:earlyProxyReferences这个set集合中,如果当前key是在循环依赖处理的后置处理器中完成了aop的动态代理,就无需再次代理了
		 */
		if (!this.earlyProxyReferences.contains(cacheKey)) {
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

这个代码,是生成aop动态代理的入口;earlyProxyReferences这个set集合,就是在调用二级缓存的getObject()方法时,会提前生成代理对象,然后,将当前bean添加到了set集合中;所以,如果bean在循环依赖的过程中,提前生成了代理对象,在调用完初始化方法回调之后,就不会再次调用生成代理对象的代码

总结
所以我们可以得知:

  1. 在依赖注入的时候,如果A注入了B,B注入了A;在A实例化的过程中,会将提前生成动态代理的代码,放到二级缓存中
  2. 在A属性注入的时候,会去实例化B,同理:将B提前生成动态代理的代码,放到了二级缓存中;B在属性注入的时候,会依赖A
  3. 此时A还未完成实例化过程,实例化A的代码还在属性注入这里,等待B的注入;那这时,B在注入A的时候,会从二级缓存中,获取到A提前生成动态代理的代码,将A生成一个代理对象,注入到B中;B接着完成初始化方法回调和动态代理的生成
  4. 在B放到单实例池之后,A接着属性注入的代码往后执行,在执行到生成动态代理这步时,由于已经生成了代理对象,会跳过;完成A的实例化
spring循环依赖源码分析 绪论 在使用spring或者springboot项目开发中,经常通过注入的方式完成一个bean对另一个bean的引用,比如classA中药注入B的bean,写法如下 @Autowired private B b 而B中要注入A的话就是下面的写法了 @Autowired private A a 用法非常简单,一目了然,用习惯了就成自然了,觉得就是那么回事,但小编最近读了点儿spring源码,就想... 阅读详情

相关推荐

基于FreeSwitch与SIP UA的蓝牙电话坐席系统实战部署

本文详细介绍了如何基于FreeSwitch开源软交换平台,结合SIP UA软电话与蓝牙电话App,在局域网内搭建一套低成本、自主可控的电话坐席系统。文章提供了从环境准备、FreeSwitch安装配置、用户与拨号计划创建,到SIP软电话测试和蓝牙设备接入的完整实战部署流程,并给出了关键优化与排坑指南,适合小微团队或个人开发者构建内部通讯工具。

zeta9的博客 544

多线程情况下如何注入Mapper,自己实例化的类如何注入其他Spring注解类

在多线程下,会发现Spring的自动注入失效了,原因是,Spring认为在多线程下这样做是危险的,如果你习惯用框架了,那么我们都知道默认是单例的,拿在多线程下如何注入Mapper,供我们使用呢? 在多线程的项目中,我也遇到了这个问题,而且我mapper接口以上的类的实例化是我自己通过java反射实例化的,我只有在Mapper接口的时候才加了@Mapper注解,导致的直接问题是,如果我如果在业务类...

ClearLoveQ的博客 4914

Harness设计——Anthropic实战:规划器、生成器、评估器三角色协作详解

本文探讨了如何通过多智能体架构突破AI编码的瓶颈问题。文章首先分析了当前AI编码面临的两大核心挑战:上下文窗口限制导致的连贯性丧失,以及自我评估机制缺失引发的质量判断偏差。针对这些问题,作者提出了基于生成对抗网络(GAN)灵感的解决方案,设计了三智能体系统:规划器负责需求分析,生成器执行编码任务,评估器进行质量审查。 实验表明,在前端设计任务中,通过制定可量化的评分标准(设计质量、原创性、工艺、功能性)并建立生成-评估反馈循环,能显著提升输出质量。这种架构随后被扩展到全栈开发领域,采用类似敏捷开发的&quo

oe1019的专栏 867

new对象如何使用 @mapper或者 @service

需求 定时任务中注册task 这里如果直接使用 @Autowired DynamicPrintTask task; 这个task的话 因为设置的是单例的 所以 不管你加多少 task 他永远只会以最后一个task 来执行所有的任务 所以这里需要用 new的形式来创建 task 但是这个task 里面 用到了 很多 mapper 如果直接 用 new 的话 这些mapper 则会报 null 所以我们需要通过 ApplicationContext 来获取上下文 解决方案 public class S

qq_38009686的博客 1768

嘉立创封装图下载后必做的3步优化(附批量删除机械层技巧)

本文针对从嘉立创下载的封装图,系统讲解了三步核心优化流程,以解决其直接使用时的常见问题。重点介绍了如何高效批量删除冗余的机械层,避免制造错误;并详细说明了规范丝印层间距、统一焊盘命名等关键步骤,帮助工程师将第三方封装转化为可靠、合规的设计资源,从而提升PCB设计效率与文件质量。

weixin_29227585的博客 433

EtherCAT APWR报文实战:从站地址配置(0x0010)的初始化流程与报文解析

本文详细解析了EtherCAT APWR报文在从站地址配置(0x0010寄存器)中的初始化流程与实战应用。通过类比物业分配门牌号,生动解释了APWR报文如何高效批量配置从站地址,并深入探讨了0x0010寄存器的特殊作用、初始化步骤及常见问题排查方法。文章结合IgH主站的三从站配置案例,提供了报文解析技巧和调试建议,帮助工程师快速掌握EtherCAT网络配置的核心技术。

weixin_28744423的博客 412

三级缓存==循环依赖?(singletonsCurrentlyInCreation缓存)

inCreationCheckExclusions更新的流程

qq_34116044的博客 1391

Xilinx PL端DP显示接口开发实战(一)——环境搭建与硬件选型

本文详细介绍了Xilinx PL端DP显示接口开发的环境搭建与硬件选型实战经验。从Vivado和Vitis 2021.1版本的软件安装要点,到DP141和MCDP6000芯片的选型建议,再到DP1.4接口的RX/TX方向开发关键点,为开发者提供了全面的技术指导。文章还分享了IP核配置、BD原理图设计以及常见问题排查的实用技巧,帮助开发者高效完成Xilinx DP接口开发项目。

weixin_29250573的博客 174

Spring之循环依赖源码解析

Spring之循环依赖底层源码解析

2201_75955594的博客 1273

​苹果开发者账号推送证书配置详细指南​

推送证书的配置是iOS应用实现消息推送的核心步骤。通过生成证书、配置描述文件、集成到服务器三大环节,开发者可快速为App启用APNs服务。建议优先使用更灵活的APNs Auth Key替代传统证书,以减少维护成本。确保拥有有效的苹果开发者账号(个人/公司账号),年费已缴纳。

2327

AI + 药物临床试验研发赛道

研究日期:2026-07-09 | 研究主题:AI + 药物临床试验研发赛道 | 聚焦岗位:Agent 平台架构师 / AI 工程架构师。

AI天才研究院 146

Spring源码分析——循环依赖

Spring源码分析——循环依赖

qq_35068291的博客 390

1.2 Spring AI V1.1.2版本 架构全景:模块拆解与设计哲学

Override// 检查请求的 Token 数量throw new TokenLimitExceededException("请求超出 Token 限制");@Override// 记录实际消耗的 Token@Override// 最先执行// 接口// 不同实现// 使用方无需改变定义抽象,保证可移植性提供 Fluent API,提升开发体验Advisor 机制提供扩展点,支持灵活定制模块化设计让你只引入需要的依赖自动配置。

小林夕程的博客 220

Spring深度学习:循环依赖及源码解析

本文小豪将带大家深度学习Spring循环依赖相关知识,包括循环依赖的解决方案及Spring处理循环依赖的底层源码,学习过程中不仅仅是了解Spring处理循环依赖的设计,更重要的是学会理解框架的底层逻辑,从而提升我们的思维能力。

Code豪客 1877

VB摄像头监控

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

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Spring 源码学习 17:循环依赖

singletonObjects:缓存某个beanName对应的经过了完整⽣命周期的beanearlySingletonObjects:缓存提前拿原始对象进⾏了AOP之后得到的代理对象,原始对象还没有进⾏属性注⼊和后续的BeanPostProcessor等⽣命周期singletonFactories:缓存的是⼀个ObjectFactory,主要⽤来去⽣成原始对象进⾏了AOP之后得到的代理对象,在每个Bean的⽣成过程中,都会提前暴露⼀个⼯⼚,这个⼯⼚可能⽤到,也可能⽤不到,

qq_51470789的博客 1056

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Spring源码学习:setter循环依赖

1.开始实例化a 1.1.在三级缓存中查找a 1.2.未找到,开始创建a的Bean实例 1.2.1.通过反射对A类实例化 1.2.2.将实例对象包装为WrappedInstance 1.2.3.将该实例添加到第三级缓存中 1.2.4.开始初始化a实例的所有属性 1.2.4.1.开始初始化a的属性b,b是setter循环依赖属性 1.2.4.2.在三级缓存中查找b 1.2.4.3.未找到,开始创建b的Bean实例 1.2.4.4.通过反射对B类实例化 1.2.4.5.将实例对象包装为WrappedInstan

qq_42418728的博客 1737

spring-core 源码学习-IoC 之加载 Bean:创建 Bean(五)之循环依赖处理(二十三)

spring-core IoC 之加载 Bean:创建 Bean(五)之循环依赖处理1. 什么是循环依赖2. 解决循环依赖2.1 getSingleton2.2 addSingletonFactory2.3 addSingleton3. 小结 这篇分析 #doCreateBean(…) 方法的第三个过程:循环依赖处理。其实,循环依赖并不仅仅只是在 #doCreateBean(…) 方法中处理,而是在整个加载 bean 的过程中都有涉及。所以,本文内容并不仅仅只局限于 #doCreateBean(…) 方法,

sunchuanwen1111的博客 206

spring 5.0.x源码学习系列八: 实例化bean之使用构造方法创建bean、自动装配与循环依赖

前言 上篇博客spring 5.0.x源码学习系列七: 后置处理器ConfigurationClassPostProcessor之BeanFactoryPostProcessor身份主要介绍了ConfigurationClassPostProcessor的BeanFactoryPostProcessor身份。接下来,将进入spring最核心的bean实例化过程总结。 一、 ...

avengerEug的博客 503

Spring源码剖析-Spring如何处理循环依赖

前言 你是不是被这个骚气的标题吸引进来的,_ 喜欢我的文章的话就给个好评吧,你的肯定是我坚持写作最大的动力,来吧兄弟们,给我一点动力 Spring如何处理循环依赖?这是最近较为频繁被问到的一个面试题,在前面Bean实例化流程中,对属性注入一文多多少少对循环依赖有过介绍,这篇文章详细讲一下Spring中的循环依赖的处理方案。 什么是循环依赖 依赖指的是Bean与Bean之间的依赖关系,循环依赖指的是两个或者多个Bean相互依赖,如: 构造器循环依赖 代码示例: public class BeanA {

墨家巨子@俏如来 6185

Spring源码最难问题《当Spring AOP遇上循环依赖》

4.AOP中的循环依赖 在看自动代理源码的过程中,突然注意到SmartInstantiationAwareBeanPostProcessor接口中的getEarlyBeanReference方法,它是Spring处理循环依赖时返回**未创建完(只实例化未做依赖注入)**Bean的扩展。关于循环依赖可以去Bean的循环依赖一章去详细了解,这里只做简单的说明。 有两个单例Bean,A和B,A中引用了B...

bugpool的博客 1万+

Spring源码解析(五):循环依赖

简单点说,就是A依赖了B,B依赖的A如果不处理,那么A创建时候需要属性填充B此时B还没有创建,那么去创建B,又需要属性填充A以此形成了死循环A依赖BB依赖A,自然排序,所以先实例化AgetBean(a),先从缓存中获取,没有,则需要去创建实例化A,创建一个对象工厂,可以通过getObje返回刚实例化的A对象,将对象工厂放入三级缓存A属性填充,需要给A中的b属性赋值,则需要getBean(b),先从缓存中获取,没有,则需要去创建。

从基础到源码解析的学习记录 976

Spring源码分析十:Spring中的循环依赖深度解析

Spring源码分析十:Spring中的循环依赖深度解析

我从不打没有准备的仗! 1万+

Spring学习——什么是循环依赖及其解决方式

在Spring框架中,循环依赖指的是当两个或多个Bean之间相互依赖,形成循环引用的情况。这种循环依赖可能会导致Spring容器无法正确地实例化Bean,从而引发应用程序启动失败或运行时异常。Spring框架通过一些特定的机制来解决循环依赖问题,如通过提前暴露Bean的引用、使用代理对象等方式来打破循环依赖,保证Bean的正确初始化和注入。

weixin_49832841的博客 2778

[spring源码]spring循环依赖

[spring源码]spring循环依赖

CUITLY_的博客 990

spring源码分析之循环依赖以及解决方案

本篇博文分几个点:什么是循环引用、循环引用的类型、spring对循环引用的解决、通过一个例子列举处理了spring中循环引用的问题,并通过对spring部分源码的分析简明扼要的解释了spring对于循环引用的结局。希望能对大家工作中遇到的这类问题有所帮助。

liushangzaibeijing的博客 6万+

Spring源码解析-Spring 循环依赖

⼀个或多个对象之间存在直接或间接的依赖关系,这种依赖关系构成⼀个环形调⽤,有下⾯ 3 种⽅式。我们看⼀个简单的 Demo,对标“情况 2”。@Service@Autowired@Service@Autowired这是⼀个经典的循环依赖,它能正常运⾏,后⾯我们会通过源码的⻆度,解读整体的执⾏流程。我们再回顾⼀下 3 级缓存的作⽤:⼀级缓存:为“Spring 的单例属性”⽽⽣,就是个单例池,⽤来存放已经初始化完成的单例 Bean;

qq_66627105的博客 1092
上一篇: spring源码-bean加载整体流程
下一篇: spring源码:九大后置处理器
小小少年_
博客等级 码龄10年 63粉丝 146原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值