一起学SF框架系列5.6-spring-Beans-AbstractBeanFactory

AbstractBeanFactory继承于FactoryBeanRegistrySupport,并负责实现ConfigurableBeanFactory,是bean核心类之一。

类作用

本类主要实现ConfigurableBeanFactory对应功能,即基于RootBeanDefinition的bean创建实现。

类实现

getBean

获得bean实现,多态实现。

@Override
	public Object getBean(String name) throws BeansException {
		return doGetBean(name, null, null, false);
	}

	@Override
	public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
		return doGetBean(name, requiredType, null, false);
	}

	@Override
	public Object getBean(String name, Object... args) throws BeansException {
		return doGetBean(name, null, args, false);
	}

doGetBean

真正实现bean实例化的主体代码。

	@SuppressWarnings("unchecked")
	protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {

		//beanName规范化(别名转换为规范名 alias->name)
		String beanName = transformedBeanName(name);
		Object beanInstance;

		//从已存在的单例池中检索,见方法getSingleton
		// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			/*单例池中存在*/
			if (logger.isTraceEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
			//获取给定bean实例的对象。对于FactoryBean,要么是FactoryBean实例本身,要么是其创建的对象
			beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}
		else {
			/*单例池中不存在*/

			//如果bean在Prototype的创建过程列表中,抛出异常
			// Fail if we're already creating this bean instance:
			// We're assumably within a circular reference.
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}

			// Check if bean definition exists in this factory.
			BeanFactory parentBeanFactory = getParentBeanFactory();
			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
			//父BeanFactory存在(即刷新前容器的BeanFactory ),且当前容器没有该bean定义
				// Not found -> check parent.
				//(可能是别名)转换为正式名称
				String nameToLookup = originalBeanName(name);
				if (parentBeanFactory instanceof AbstractBeanFactory abf) {
					//父BeanFactory是AbstractBeanFactory ,用父BeanFactory创建bean
					return abf.doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
				}
				else if (args != null) {
					//难于理解父BeanFactory是AbstractBeanFactory?
					//父BeanFactory不是AbstractBeanFactory,用父BeanFactory带args创建bean
					// Delegation to parent with explicit args.
					return (T) parentBeanFactory.getBean(nameToLookup, args);
				}
				else if (requiredType != null) {
					//父BeanFactory不是AbstractBeanFactory,用父BeanFactory带requiredType创建bean
					// No args -> delegate to standard getBean method.
					return parentBeanFactory.getBean(nameToLookup, requiredType);
				}
				else {
					//父BeanFactory不是AbstractBeanFactory,用父BeanFactory创建bean
					return (T) parentBeanFactory.getBean(nameToLookup);
				}
			}

			// 如果不是仅检查类型,把beanName加入到已创建bean的beanName缓存池alreadyCreated
			if (!typeCheckOnly) {
				markBeanAsCreated(beanName);
			}

			/*开始创建bean*/
			StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate")
					.tag("beanName", name);
			try {
				if (requiredType != null) {
					beanCreation.tag("beanType", requiredType::toString);
				}
				//对于继承bean,合并所有层次的bean定义
				RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				//检查bean,如果是抽象的或其它不能实例化情况则抛出异常
				checkMergedBeanDefinition(mbd, beanName, args);

				/* 先创建依赖的bean */
				// Guarantee initialization of beans that the current bean depends on.
				String[] dependsOn = mbd.getDependsOn();
				if (dependsOn != null) {
					for (String dep : dependsOn) {
						//检查bean是否循环依赖
						if (isDependent(beanName, dep)) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
						}
						//注册bean依赖关系(注意同isDependent的参数顺序刚好反过来) 注2
						registerDependentBean(dep, beanName);
						try {
							//获得依赖bean的实例
							getBean(dep);
						}
						catch (NoSuchBeanDefinitionException ex) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
						}
					}
				}

				/* 创建bean自身 */
				// Create bean instance.
				if (mbd.isSingleton()) {
				//单例bean
					// 此处beanFactory用了匿名函数 注1
					sharedInstance = getSingleton(beanName, () -> {
						try {
							//注3
							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;
						}
					});
					//获取给定bean实例的对象。对于FactoryBean,要么是FactoryBean实例本身,要么是其创建的对象
					beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}

				else if (mbd.isPrototype()) {
				//Prototype模式bean--每次都生成新bean
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
						//加入正在创建缓存池prototypesCurrentlyInCreation
						beforePrototypeCreation(beanName);
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						//从正在创建缓存池prototypesCurrentlyInCreation删除
						afterPrototypeCreation(beanName);
					}
					//获取给定bean实例的对象。对于FactoryBean,要么是FactoryBean实例本身,要么是其创建的对象
					beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

				else {
				//其它模式bean
					String scopeName = mbd.getScope();
					if (!StringUtils.hasLength(scopeName)) {
						throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
					}
					Scope scope = this.scopes.get(scopeName);
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {
						Object scopedInstance = scope.get(beanName, () -> {
							//加入正在创建缓存池prototypesCurrentlyInCreation
							beforePrototypeCreation(beanName);
							try {
								return createBean(beanName, mbd, args);
							}
							finally {
								//从正在创建缓存池prototypesCurrentlyInCreation删除
								afterPrototypeCreation(beanName);
							}
						});
						//获取给定bean实例的对象。对于FactoryBean,要么是FactoryBean实例本身,要么是其创建的对象
						beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
						throw new ScopeNotActiveException(beanName, scopeName, ex);
					}
				}
			}
			catch (BeansException ex) {
				beanCreation.tag("exception", ex.getClass().toString());
				beanCreation.tag("message", String.valueOf(ex.getMessage()));
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
			finally {
				beanCreation.end();
			}
		}

		//检查beanInstance是否符合requiredType,最后return (T)beanInstance
		return adaptBeanInstance(name, beanInstance, requiredType);
	}

注1:getSingleton方法见:https://blog.csdn.net/davidwkx/article/details/131011751
注2:isDependent和registerDependentBean参数顺序刚好反过来,对比着看代码易懂
注3:createBean在子类AbstractAutowireCapableBeanFactory实现,见:https://blog.csdn.net/davidwkx/article/details/130966338

getSingleton(String beanName)

获取单实例bean

	@Override
	@Nullable
	public Object getSingleton(String beanName) {
		return getSingleton(beanName, true);
	}

	/**
	 * Return the (raw) singleton object registered under the given name.
	 * <p>Checks already instantiated singletons and also allows for an early
	 * reference to a currently created singleton (resolving a circular reference).
	 * @param beanName the name of the bean to look for
	 * @param allowEarlyReference whether early references should be created or not
	 * @return the registered singleton object, or {@code null} if none found
	 */
	@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		//从singletonObjects和earlySingletonObjects查找bean实例 注2
		// Quick check for existing instance without full singleton lock
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				//两阶段检测法 注1
				synchronized (this.singletonObjects) {
					// Consistent creation of early reference within full singleton lock
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								//是FactoryBean,通过getgetObject创建工厂中的bean
								singletonObject = singletonFactory.getObject();
								this.earlySingletonObjects.put(beanName, singletonObject);
								//单实例模式下,FactoryBean仅用于创建一次bean,用后就删除
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

注1:要理解这段代码,需要明白下述缓存池的作用:
  singletonObjects:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用。
  earlySingletonObjects:提前创建的单例对象cache,存放原始的 bean 对象(已构建,但尚未填充属性)。
  singletonFactories:单例对象工厂的cache,存放 bean 工厂对象。

注2:采用两阶段检测法从singletonObjects和earlySingletonObjects查找bean实例,第一次不采用同步锁是为了提高查找效率;如果没查到,第二次查找采用同步锁,原因在于多线程中其它线程可能由于bean的循环引用导致生成该bean实例(第一次查找时还没有生成),可防止漏查或重复生成Bean实例。

getSingleton(String beanName, ObjectFactory<?> singletonFactory)

返回在给定名称下注册的(原始)singleton对象,如果尚未注册,则创建并注册一个新对象

	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
		synchronized (this.singletonObjects) {
			//从单实例bean缓存池singletonObjects直接获取
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				/*缓存池不存在*/
				if (this.singletonsCurrentlyInDestruction) {
					throw new BeanCreationNotAllowedException(beanName,
							"Singleton bean creation not allowed while 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 + "'");
				}
				//创建前放入单实例正在创建缓存池singletonsCurrentlyInCreation
				beforeSingletonCreation(beanName);
				
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<>();
				}
				try {
					//从创建该bean的工厂示例中获取bean
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
					//异常时,再次从单实例bean缓存池singletonObjects获取(其它线程产生的)
					// 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;
					}
					//从单实例正在创建缓存池singletonsCurrentlyInCreation删除
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					//如果是新示例,加入缓存池singletonObjects、registeredSingletons,并从缓存池singletonFactories、earlySingletonObjects删除
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

getObjectForBeanInstance

获取给定bean实例的对象。如果获取的是FactoryBean的情况下,要么是bean实例本身(beanName的带FactoryBean前缀),要么是其创建的对象(beanName的不带FactoryBean前缀)。

protected Object getObjectForBeanInstance(
			Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
		// Don't let calling code try to dereference the factory if the bean isn't a factory.
		//beanName的带FactoryBean前缀,获取的是factoryBean本身
		if (BeanFactoryUtils.isFactoryDereference(name)) {
			if (beanInstance instanceof NullBean) { 
			//NullBean直接返回
				return beanInstance;
			}
			if (!(beanInstance instanceof FactoryBean)) {
				throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
			}
			if (mbd != null) {
			//标注合并后的RootBeanDefinition本身为factoryBean
				mbd.isFactoryBean = true;
			}
			//返回自身
			return beanInstance;
		}

		// Now we have the bean instance, which may be a normal bean or a FactoryBean.
		// If it's a FactoryBean, we use it to create a bean instance, unless the
		// caller actually wants a reference to the factory.
		//beanName的未带FactoryBean前缀,bean类型也不是factoryBean即普通bean,直接返回自身
		if (!(beanInstance instanceof FactoryBean<?> factoryBean)) {
			return beanInstance;
		}

		//beanName的未带FactoryBean前缀,但bean类型是factoryBean,需返回factoryBean产生的bean
		Object object = null;
		if (mbd != null) {
			mbd.isFactoryBean = true;
		}
		else {
			//factoryBean产生bean(getObject()的结果)的缓存池获取
			object = getCachedObjectForFactoryBean(beanName);
		}
		if (object == null) {
			/*缓存池没有,就让factoryBean产生bean*/

			// Return bean instance from factory.
			// Caches object obtained from FactoryBean if it is a singleton.
			//传入mbd为空,但存在该bean的定义,则补充生成mdb
			if (mbd == null && containsBeanDefinition(beanName)) {
				mbd = getMergedLocalBeanDefinition(beanName);
			}
			//synthetic(合成的)指BeanDefinition是在应用中明确定义的还是框架按需要合成的
			boolean synthetic = (mbd != null && mbd.isSynthetic());
			//factoryBean产生object 注1
			object = getObjectFromFactoryBean(factoryBean, beanName, !synthetic);
		}
		return object;
	}

注1:见FactoryBeanRegistrySupport实现https://blog.csdn.net/davidwkx/article/details/131010290

getMergedLocalBeanDefinition

把当前bean定义同父bean合并为RootBeanDefinition(RootBeanDefinition更恰当可成为”完整bean定义“,即合并了所有继承的层级bean定义)。
其它多态的getMergedLocalBeanDefinition都是调用到这个getMergedLocalBeanDefinition。

	protected RootBeanDefinition getMergedBeanDefinition(
			String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
			throws BeanDefinitionStoreException {

		synchronized (this.mergedBeanDefinitions) {
			RootBeanDefinition mbd = null;
			RootBeanDefinition previous = null;

			// Check with full lock now in order to enforce the same merged instance.
			if (containingBd == null) {
				mbd = this.mergedBeanDefinitions.get(beanName);
			}

			if (mbd == null || mbd.stale) {
				previous = mbd;
				if (bd.getParentName() == null) {
					// Use copy of given root bean definition.
					if (bd instanceof RootBeanDefinition rootBeanDef) {
						mbd = rootBeanDef.cloneBeanDefinition();
					}
					else {
						mbd = new RootBeanDefinition(bd);
					}
				}
				else {
					// Child bean definition: needs to be merged with parent.
					BeanDefinition pbd;
					try {
						String parentBeanName = transformedBeanName(bd.getParentName());
						if (!beanName.equals(parentBeanName)) {
							pbd = getMergedBeanDefinition(parentBeanName);
						}
						else {
							if (getParentBeanFactory() instanceof ConfigurableBeanFactory parent) {
								pbd = parent.getMergedBeanDefinition(parentBeanName);
							}
							else {
								throw new NoSuchBeanDefinitionException(parentBeanName,
										"Parent name '" + parentBeanName + "' is equal to bean name '" + beanName +
												"': cannot be resolved without a ConfigurableBeanFactory parent");
							}
						}
					}
					catch (NoSuchBeanDefinitionException ex) {
						throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName,
								"Could not resolve parent bean definition '" + bd.getParentName() + "'", ex);
					}
					// Deep copy with overridden values.
					mbd = new RootBeanDefinition(pbd);
					mbd.overrideFrom(bd);
				}

				// 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.
				if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
					mbd.setScope(containingBd.getScope());
				}

				// Cache the merged bean definition for the time being
				// (it might still get re-merged later on in order to pick up metadata changes)
				if (containingBd == null && isCacheBeanMetadata()) {
					this.mergedBeanDefinitions.put(beanName, mbd);
				}
			}
			if (previous != null) {
				copyRelevantMergedBeanDefinitionCaches(previous, mbd);
			}
			return mbd;
		}
	}

isDependent

检查bean是否循环依赖。
其它多态的isDependent都是调用到这个isDependent。

	private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set<String> alreadySeen) {
		//已经检查过不是循环依赖
		if (alreadySeen != null && alreadySeen.contains(beanName)) {
			return false;
		}
		
		//转换成规范名称处理
		String canonicalName = canonicalName(beanName);
		//从依赖bean池中检索出所有依赖当前bean的beanName
		Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
		if (dependentBeans == null) {
			return false;
		}
		//直接循环依赖:如果依赖该bean的beanName集合包含dependentBeanName,肯定是循环依赖
		if (dependentBeans.contains(dependentBeanName)) {
			return true;
		}
		for (String transitiveDependency : dependentBeans) {
		//间接循环依赖 处理
			if (alreadySeen == null) {
				alreadySeen = new HashSet<>();
			}
			//当前beanName加入到已处理的alreadySeen
			alreadySeen.add(beanName);
			//递归是否循环依赖:transitiveDependency就是依赖beanName的beanName
			if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {
				//只要有一个是循环依赖,则结束循环直接返回
				return true;
			}
		}
		return false;
	}

resolveBeanClass / doResolveBeanClass

加载bean的类文件(即类加载)。

	@Nullable
	protected Class<?> resolveBeanClass(RootBeanDefinition mbd, String beanName, Class<?>... typesToMatch)
			throws CannotLoadBeanClassException {

		try {
			// 判断 BeanDefinition 中的 beanClass 是不是 Class 类型的
			if (mbd.hasBeanClass()) {
				return mbd.getBeanClass();
			}
			//新创建BeanClass返回
			return doResolveBeanClass(mbd, typesToMatch);
		}
		catch (ClassNotFoundException ex) {
			throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);
		}
		catch (LinkageError err) {
			throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err);
		}
	}

	/* 为bean定义找到bean类,将bean类名解析为class引用(如果需要),并将找到的class存储在BeanDefinition中以供进一步使用 */
	@Nullable
	private Class<?> doResolveBeanClass(RootBeanDefinition mbd, Class<?>... typesToMatch)
			throws ClassNotFoundException {

		//获取类加载器 注1
		ClassLoader beanClassLoader = getBeanClassLoader();
		ClassLoader dynamicLoader = beanClassLoader;
		boolean freshResolve = false;

		//参数typesToMatch处理
		if (!ObjectUtils.isEmpty(typesToMatch)) {
			// When just doing type checks (i.e. not creating an actual instance yet),
			// use the specified temporary class loader (e.g. in a weaving scenario).
			ClassLoader tempClassLoader = getTempClassLoader();
			if (tempClassLoader != null) {
				dynamicLoader = tempClassLoader;
				freshResolve = true;
				if (tempClassLoader instanceof DecoratingClassLoader dcl) {
					for (Class<?> typeToMatch : typesToMatch) {
						dcl.excludeClass(typeToMatch.getName());
					}
				}
			}
		}

		//获取bean类的名称 注2
		String className = mbd.getBeanClassName();
		if (className != null) {
			//如果className是表达式,求取表达式值
			Object evaluated = evaluateBeanDefinitionString(className, mbd);
			if (!className.equals(evaluated)) {
				//evaluate前后发生变化
				// A dynamically resolved expression, supported as of 4.2...
				if (evaluated instanceof Class<?> clazz) {
					//Class类型表示已经是加载类了
					return clazz;
				}
				else if (evaluated instanceof String name) {
					//字符串类型表示只是beanName,需要加载类
					className = name;
					freshResolve = true;
				}
				else {
					throw new IllegalStateException("Invalid class name expression result: " + evaluated);
				}
			}
			if (freshResolve) {
				//临时加载beanClass,不缓存到this.beanClass
				// When resolving against a temporary class loader, exit early in order
				// to avoid storing the resolved Class in the bean definition.
				if (dynamicLoader != null) {
					try {
						//用dynamicLoader加载类
						return dynamicLoader.loadClass(className);
					}
					catch (ClassNotFoundException ex) {
						if (logger.isTraceEnabled()) {
							logger.trace("Could not load class [" + className + "] from " + dynamicLoader + ": " + ex);
						}
					}
				}
				//用ClassUtils.forName加载类
				return ClassUtils.forName(className, dynamicLoader);
			}
		}

		//由RootBeanDefinition加载类,并缓存到this.beanClass
		// Resolve regularly, caching the result in the BeanDefinition...
		return mbd.resolveBeanClass(beanClassLoader);
	}

注1:getBeanClassLoader()调用ClassUtils.getDefaultClassLoader(),其获得加载器优先顺序是:当前线程的类加载器-加载ClassUtils的类加载器-应用初始化用到的类加载器。只有前一种不存在,才取获得后一种。由于可以按线程获得类加载器,因此spring支持多线程类加载。
注2:getBeanClassName()是通过this.beanClass获得的,但在本类中beanClass存在两种值,没有加载bean类前,保存beanName;加载后,保存beanClass;因此getBeanClassName()实现如下:

	@Override
	@Nullable
	public String getBeanClassName() {
		return (this.beanClass instanceof Class<?> clazz ? clazz.getName() : (String) this.beanClass);
	}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乐享技术

每一个打赏,都是对我最大的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值