Spring源码系列-Spring的Bean的生命周期【上】

Spring的Bean的生命周期【上】

1.关于spring的FactoryBean的问题:

@Component()
public class LubanFactoryBean implements FactoryBean {

	public Object getObject() throws Exception {
		return new User();
	}

	public Class<?> getObjectType() {
		return User.class;
	}

}


public class Test {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

		System.out.println(applicationContext.getBean("lubanFactoryBean"));
		System.out.println(applicationContext.getBean("lubanFactoryBean"));
		System.out.println(applicationContext.getBean("lubanFactoryBean"));


	}
}


com.luban.entity.User@5aaa6d82
com.luban.entity.User@5aaa6d82
com.luban.entity.User@5aaa6d82

问题1:getBean的时候,为什么get的对象对象都是同一个对象呢?

default boolean isSingleton() {
  return true;
}
FactoryBean有一个default方法:isSingleton,默认是单例
那么我们就知道虽然getBean了三次,但是getObject()就只执行了一次

问题2:那么在执行getObject()的时候是在哪一行代码呢

应该是在第一次调用getBean()的时候执行的

那么在启动spring的时候,做些什么呢?

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

我们在启动spring的时候,就会去创建非懒加载的单例bean

我们的LubanFactoryBean上面有一个@Componet,Spring启动的时候就去扫描,并把它当作普通的

bean,所以在执行完这行代码之后,我们在单例池中有LubanFactoryBean
在这里插入图片描述
我们看到这个LubanFactoryBeanDefinition是通过扫描得到的

问题3:为什么往单例池放数据的时候是LubanFactoryBean,在getBean的时候却是User呢?

流程:在getBean的时候,会把beanName转化为一种eg:lubanFactoryBean,然后在单例池中找到这个对象

LubanFactoryBean,然后判断这个对象是不是FactoryBean,不是直接返回,是则从一个缓存map中去拿,如果缓存map不存在这个对象,就强制转换为FactoryBean,然后调用getObject方法,放到缓存map里面。

我们点击getBean的源码进去看 (实现类:AbstactBeanFctory)

---------------------------------transformedBeanName-----------------------------------
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

		// 对beanName进行转换 name如果是"&lubanFactoryBean",那么beanName就是"lubanFactoryBean"
		final String beanName = transformedBeanName(name);
		Object bean;
    //这个方法的作用就是 不管你传过来的name:lubanFactoryBean,lubanFactoryBean1(别名),&lubanFactoryBean,一律转化为lubanFactoryBean,因为我要用它来进行获取单例池中的对象(此例:LubanFactoryBean对象)
    
  --------------------------- transformedBeanName(name)转换beanName-----------------------  
     * 返回beanName,去掉FactoryBean的&前缀,并且把name当做别名去aliasMap中寻找原始的beanName
	 */
	protected String transformedBeanName(String name) {  // &lubanFactoryBean lubanFactoryBean1 user1  &beanName
		return canonicalName(BeanFactoryUtils.transformedBeanName(name));
	}
    
  -----------------------------  doGetBean-----------------------------------------------
    	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

		// 对beanName进行转换 name如果是"&lubanFactoryBean",那么beanName就是"lubanFactoryBean"
		final String beanName = transformedBeanName(name);
		Object bean;

		// Eagerly check singleton cache for manually registered singletons.
      //从单例池中去拿,此时beanName:ubanFactoryBean,那么肯定可以拿到对象LubanFactoryBean
		Object sharedInstance = getSingleton(beanName);  // Map<>
		if (sharedInstance != null && args == null) {
			// 判断sharedInstance是不是FactoryBean,如果是FactoryBean,那么真正需要拿到的是getObject方法所返回的对象
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}
---------------------------getObjectForBeanInstance-判断是不是FactoryBean--------------
// 判断beanInstance是不是FactoryBean
	// 如果是FactoryBean
		// 如果name是以&开头,那么则返回beanInstance
		// 如果name不是以&开头,那么返回的是getObject方法中所返回的对象(注意如果是单例,则会进行缓存)
	// 如果不是FactoryBean,那么返回的是beanInstance
	protected Object getObjectForBeanInstance(
			Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

		// 如果是&lubanFactoryBean,那么则直接返回单例池(SingletonObjects)中的对象
		if (BeanFactoryUtils.isFactoryDereference(name)) {
			if (beanInstance instanceof NullBean) {
				return beanInstance;
			}
			if (!(beanInstance instanceof FactoryBean)) {
				throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
			}
			if (mbd != null) {
				mbd.isFactoryBean = true;
			}
			return beanInstance;
		}

		//如果不是FactoryBean,直接返回.
		if (!(beanInstance instanceof FactoryBean)) {
			return beanInstance;
		}

		// 如果beanInstance是FactoryBean,并且name也不是以&开头
		Object object = null;
		if (mbd != null) {
			mbd.isFactoryBean = true;
		}
		else {
			object = getCachedObjectForFactoryBean(beanName);
		}

		// 从factoryBeanObjectCache中没有拿到则进行创建
		if (object == null) {
			FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
			if (mbd == null && containsBeanDefinition(beanName)) {
				mbd = getMergedLocalBeanDefinition(beanName);
			}
			boolean synthetic = (mbd != null && mbd.isSynthetic());
			// 调用getObject方法得到对象并放入factoryBeanObjectCache中
			object = getObjectFromFactoryBean(factory, beanName, !synthetic);
		}
		return object;
	}

-----------------------getCachedObjectForFactoryBean----------------------
 protected Object getCachedObjectForFactoryBean(String beanName) {
		return this.factoryBeanObjectCache.get(beanName);
	}//factoryBeanObjectCache是一个cocurretHashMap,一开始getBean的时候肯定没有
//就会走下面的逻辑,首先把beanInstance强制转化为FactoryBean,这样我们可以调用getObject方法,

---------------------------getObjectFromFactoryBean------------------------------
    
    protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {//factory就是LubanFactoryBean,默认isSingleton()为true
		// 是不是单例的
		if (factory.isSingleton() && containsSingleton(beanName)) {
			synchronized (getSingletonMutex()) {
				Object object = this.factoryBeanObjectCache.get(beanName);
				if (object == null) {
					// 调用getObject方法得到一个对象
					object = doGetObjectFromFactoryBean(factory, beanName);
					// Only post-process and store if not put there already during getObject() call above
                    
----------------------------------doGetObjectFromFactoryBean--------------------------
    	private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)
			throws BeanCreationException {

		Object object;
		try {
			if (System.getSecurityManager() != null) {
				AccessControlContext acc = getAccessControlContext();
				try {
					object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
				}
				catch (PrivilegedActionException pae) {
					throw pae.getException();
				}
			}
			else {
				object = factory.getObject();//是单例就直接去调用FactoryBean的getobject()方法
			}
-------------------------------doGetObjectFromFactoryBean----------------------------
					// (e.g. because of circular reference processing triggered by custom getBean calls)
					Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
					if (alreadyThere != null) {
						object = alreadyThere;
					}
					else {
						if (shouldPostProcess) {
							// 单例真正创建
							if (isSingletonCurrentlyInCreation(beanName)) {
								// Temporarily return non-post-processed object, not storing it yet..
								return object;
							}
							beforeSingletonCreation(beanName);
							try {
								// 调用BeanPostProcessor执行初始化后的逻辑,主要就是进行AOP
								object = postProcessObjectFromFactoryBean(object, beanName);
							}
							catch (Throwable ex) {
								throw new BeanCreationException(beanName,
										"Post-processing of FactoryBean's singleton object failed", ex);
							}
							finally {
								afterSingletonCreation(beanName);
							}
						}
       **************************//放入到我们的factoryBeanObjectCache(cocurrentHashMap)中
						if (containsSingleton(beanName)) {
							this.factoryBeanObjectCache.put(beanName, object);
						}
					}
				}
				return object;
			}
		}
		else {
			// 多例
			Object object = doGetObjectFromFactoryBean(factory, beanName);
			if (shouldPostProcess) {
				try {
					object = postProcessObjectFromFactoryBean(object, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
				}
			}
			return object;
		}
	}

问题4:FactoryBean用在哪儿呢?

Spring整合mybatis的时候就可以使用FactoryBean

我们在getObject中就可以把Mapper的一个实现类加入到我们spring的容器里面了,

但是这种方式和@Bean,又什么区别呢?

我们的@Bean只能是在某个方法上,加上@Bean,但是如果一个类实现了@FactoryBean,它也可以实现其他的接口,

@Component()
public class LubanFactoryBean implements FactoryBean, BeanClassLoaderAware {

	public Object getObject() throws Exception {
		return new User();
	}

	public Class<?> getObjectType() {
		return User.class;
	}

	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		
	}
}

我们的FactoryBean比@Bean更加的强大一些。
在这里插入图片描述

问题5:ApplicationContext为什么实现了BeanFactory,在其内部还存在DefaultListableBeanFactory

最重要的就是BeanFctory:

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {

   private final DefaultListableBeanFactory beanFactory;

如果我们实现了某个接口,就代表着我们要实现它的方法,那么ApplicationContext是怎么实现它的这些方法的呢,调用DefaultListableBeanFactory,因为这个DefaultListableBeanFactory功能已经很强大了,所以我只要调用它的方法就好了,是一种委托。

2.Spring的生命周期

进行扫描–>生成BeanDefinition–>合并BeanDefinition–>加载类–>实例化前–>推断构造方法–>实例化

问题1:进行扫描

如果让你进行扫描,你会怎么做?

1.Spring进行扫描的时候不会加载,生成class文件,而是使用asm直接去判断字节码

2.生成BeanDefinition

-----------refresh();----------------------
-----------	// 完成beanFactory的初始化(实例化非懒加载的单例bean)
			finishBeanFactoryInitialization(beanFactory);--------------------------
//实例化所有非懒加载的bean,也就是怎么来创建bean
----------beanFactory.preInstantiateSingletons();----------------------------

我们来看一下继承的BeanDefinition

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xmlns:aop="http://www.springframework.org/schema/aop"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
	   https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

	<bean id="parent" scope="prototype" abstract="true"></bean>
	<bean id="userService" class="com.luban.service.UserService" parent="parent"></bean>



</beans>
//我们来验证获得的userService是不是原型
public class Test {

	public static void main(String[] args) {
		ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");

		System.out.println(applicationContext.getBean("userService"));
		System.out.println(applicationContext.getBean("userService"));
		System.out.println(applicationContext.getBean("userService"));


	}
}


com.luban.service.UserService@5387f9e0
com.luban.service.UserService@6e5e91e4
com.luban.service.UserService@2cdf8d8a

此时在beanDefinitionMap中是GenericBeanDefinition类型,是根据bean的属性来进行生成的
在这里插入图片描述
在mergeBeanDefinitions就是RootBeanDefininiton类型,是GenericBeanDefinition合并得到的

在这里插入图片描述

我们可以看一下它们各自是什么BeanDefinition

在beanDefinitionMap的时候类型是:GenericBeanDefinition,这个可以设置它们的ParentName

@Override
	public void setParentName(@Nullable String parentName) {
		this.parentName = parentName;
	}

	@Override
	@Nullable
	public String getParentName() {
		return this.parentName;
	}

在合并之后得到的(mergeBeanDefinitions)是RootBeanDefinition,setParentName是时候会报错,就不再又ParentBeanDefinition

@Override
	public String getParentName() {
		return null;
	}

	@Override
	public void setParentName(@Nullable String parentName) {
		if (parentName != null) {
			throw new IllegalArgumentException("Root bean cannot be changed into a child bean with parent reference");
		}
	}

源码:是怎么样获得RootBeanDefinition

--------------------preInstantiateSingletons----------------------------------
@Override
	public void preInstantiateSingletons() throws BeansException {

         //获取所有的beanNames
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		// 循环bd,实例化单例bean
		for (String beanName : beanNames) {  // userService
			// 对beanDefinition进行合并,基于合并后的BeanDefinition去创建bean
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
-----------------getMergedLocalBeanDefinition(beanName)-----------------------------
    
    protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
			// Quick check on the concurrent map first, with minimal locking.
			RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
			if (mbd != null && !mbd.stale) {
				return mbd;
			}//如果mergedBeanDefinitions存在直接返回,否则遍历beanDefinitionMap进行合并
			return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
     } 
-----------------父bd和子bd进行合并---------------------------------------------------------
------getMergedBeanDefinition(
			String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
			throws BeanDefinitionStoreException--------------------------------------
            
            if (mbd == null || mbd.stale) {
				previous = mbd;
				// 如果bd的父bd为空,那么就直接把bd封装为RootBeanDefinition
				if (bd.getParentName() == null) {
					// Use copy of given root bean definition.
					// RootBeanDefinition没有父BeanDefinition
					if (bd instanceof RootBeanDefinition) {
						mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();
					}
					else {
						mbd = new RootBeanDefinition(bd);
					}
				}
				else {//如果父bd不为空,那就递归的去获取<bean abstact=true>的bean
					BeanDefinition pbd;
					try {
						// 父bd的beanName
						String parentBeanName = transformedBeanName(bd.getParentName());
						if (!beanName.equals(parentBeanName)) {
							pbd = getMergedBeanDefinition(parentBeanName);
						}
						else {
							BeanFactory parent = getParentBeanFactory();
							if (parent instanceof ConfigurableBeanFactory) {
								pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName);
							}
							else {
								throw new NoSuchBeanDefinitionException(parentBeanName,
										"Parent name '" + parentBeanName + "' is equal to bean name '" + beanName +
										"': cannot be resolved without an AbstractBeanFactory parent");
							}
						}
					}
                    //把子bd的属性赋值给父属性
					// pbd表示父BeanDefinition, bd表示本BeanDefinition
					mbd = new RootBeanDefinition(pbd);
					mbd.overrideFrom(bd);	// 把bd的属性设置给mbd, 而mbd是基于pbd来的,pbd是parent,bd是当前bd,把当前bd的属性设置给pbd就是合并(合并属性)
				}
                
				// Set default singleton scope, if not configured before.
				if (!StringUtils.hasLength(mbd.getScope())) {
					mbd.setScope(SCOPE_SINGLETON);
				}

				// A bean contained in a non-singleton bean cannot be a singleton itself.
				// Let's correct this on the fly here, since this might be the result of
				// parent-child merging for the outer bean, in which case the original inner bean
				// definition will not have inherited the merged outer bean's singleton status.
				// 如果某个<bean/>内包含了一个内部<bean/>,containingBd表示外部bean, mbd表示内部bean
				// 外部bean如果不是单例bean,内部bean是单例的,那么则把内部bean的scope设置为和外部bean的scope一样的
				if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
					mbd.setScope(containingBd.getScope());
				}

***********************************合并完成就放入到mergedBeanDefinitions中*****************
				if (containingBd == null && isCacheBeanMetadata()) {
					this.mergedBeanDefinitions.put(beanName, mbd);
				}

继续往下走逻辑:
在这里插入图片描述
根据BeanName去创建Bean:

// 非抽象,单例,非懒加载
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {

				// 判断是不是一个SmartFactoryBean
				if (isFactoryBean(beanName)) {
					//  如果是一个FactoryBean,那么则获取LubanFactoryBean对象
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof FactoryBean) {
						final FactoryBean<?> factory = (FactoryBean<?>) bean;
						boolean isEagerInit;
						// eager:急切的意思,立马初始化
						if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
							isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
											((SmartFactoryBean<?>) factory)::isEagerInit,
									getAccessControlContext());
						}
						else {
							isEagerInit = (factory instanceof SmartFactoryBean &&
									((SmartFactoryBean<?>) factory).isEagerInit());
						}
						if (isEagerInit) {
							// 根据beanName去生成FactoryBean中所生成的Bean对象
							getBean(beanName);
						}
					}
				}
				else {
					// 根据beanName去创建bean
					getBean(beanName);
				}
			}
public class LubanFactoryBean1  implements SmartFactoryBean {
	@Override
	public Object getObject() throws Exception {
		return null;
	}

	@Override
	public Class<?> getObjectType() {
		return null;
	}

	@Override
	public boolean isEagerInit() {//return false的时候,跟FactoryBean没什么区别
		                         // return true的时候就不是懒加载了
		return false;
	}
}

在getBean的时候就是去创建Bean,

点击进去getBean看源码:

在doGetBean中流程:

首先获取到合并的beanDefinition对象,然后看对象是否存在循环依赖,没有循环依赖有DependOn的时候就

先去创建依赖的哪个对象
在这里插入图片描述
创建bean解决循环依赖的问题:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
			
    try {
        // 得到合并后的BeanDefinition
        final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
        checkMergedBeanDefinition(mbd, beanName, args);

        //循环依赖**************
        // 加载DependsOn的bean
        String[] dependsOn = mbd.getDependsOn();
        if (dependsOn != null) {
            for (String dep : dependsOn) {
                // 判断beanName是不是也被dep依赖了,如果是,就是相互依赖
                if (isDependent(beanName, dep)) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
       "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
         }
                //在isDependent(beanName,dep)中:存在了两个map进行判断是不是存在循环依赖
                // 存在在两个map中
                // 1. dependentBeanMap,key为dep, value是一个LinkedHashSet,表示dep被哪些bean依赖了
                // 2. dependenciesForBeanMap,key为beanName,value是一个LinkedHashSet,表示beanName依赖了哪些bean
						}
            registerDependentBean(dep, beanName);
            try {
                // 先去生成所依赖的bean
                getBean(dep); //  getBean("xxx")
            }
            catch (NoSuchBeanDefinitionException ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                   "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
            }

            
          

创建bean解决Scope的问题:

if (mbd.isSingleton()) {
					// 获取单例bean,如果获取不到则创建一个bean,并且放入单例池中
					sharedInstance = getSingleton(beanName, () -> {
						try {
								return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							destroySingleton(beanName);
							throw ex;
						}
					});
					// sharedInstance可能是一个FactoryBean,所以需要单独再去  factoryBeanObjectCache中去获取对应的对象
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
else if (mbd.isPrototype()) {
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
						beforePrototypeCreation(beanName);//记录一下开始产生bean
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						afterPrototypeCreation(beanName);//记录产生bean结束,主要是为了循环依赖
					}
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

CreateBean方法 BeanPostProcessor

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {
      
      // 1、实例化前 null
      Object bean = ***resolveBeforeInstantiation(beanName, mbdToUse);  // 对象
      if (bean != null) {
         return bean;
      }
   }
   ----------------------------------------------------------------------------------
   ----------------------------------------------------------------------------------
   try {
      // 创建bean   Spring自带的创建bean的方法
      Object beanInstance = doCreateBean(beanName, mbdToUse, args);
      if (logger.isTraceEnabled()) {
         logger.trace("Finished creating instance of bean '" + beanName + "'");
      }
      return beanInstance;
   }
其实resolveBeforeInstantiation就是给我们程序员一个扩展,bean!=null,就不需要去执行Spring自带
         的创建bean的方法,那么我们怎么去扩展呢?
         
         protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
		Object bean = null;
		// beforeInstantiationResolved为null或true
		if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
			// Make sure bean class is actually resolved at this point.
			if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
				Class<?> targetType = determineTargetType(beanName, mbd);
				if (targetType != null) {
					// 实例化前***
					bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
					if (bean != null) {
						bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
					}
				}
			}
			mbd.beforeInstantiationResolved = (bean != null);
		}
		return bean;
	}

在这个实例化前

bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
@Nullable
	protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof InstantiationAwareBeanPostProcessor) {
				InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
				Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
				if (result != null) {
					return result;
				}
			}
		}
		return null;
	}
我们可以去获取到InstantiationAwareBeanPostProcessor类型的后置处理器,调用postProcessBeforeInstantiation的方法,产生对象,这样bean!=null.我们就不用去执行Spring的create逻辑了

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值