Spring getBean(beanName)流程

文章详细阐述了Spring框架中bean的初始化过程,包括从三级缓存获取bean、依赖注入、调用`getObject()`方法以及执行初始化方法如`afterPropertiesSet()`。在初始化过程中,Spring处理了循环依赖、属性注入、以及实现了`InitializingBean`接口和自定义`init-method`的调用。

在这里插入图片描述
在这里插入图片描述

上节分析到refresh()方法中finishBeanFactoryInitialization(beanFactory)中beanFactory.preInstantiateSingletons();循环所有的beanNames,调用getBean(beanName)

先学习一个方法:

    public static void AAA(ObjectFactory<?> objectFactory){
         objectFactory.getObject(); //调用getObject()方法实际调用的是BBB()方法
       }
     public static  Object BBB(String a){
        System.out.println(a);
          return "666";
 }
 public static void main(String[] args) {
     AAA(()->BBB("abc"));
 }

日志:

abc
  `genBean(beanName)主讲两个方法:`

一、Object sharedInstance = getSingleton(beanName);

	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	    //一级缓存取
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		//二级缓存取
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
					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) {
							//此处调用getObject()方法,实际调用的是doCreateBean()中addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)) 中的getEarlyBeanReference(beanName, mbd, bean)方法,这个方法执行过切面了,生成了未注入属性,为初始化的半成品bean,并放入二级缓存
								singletonObject = singletonFactory.getObject();
								this.earlySingletonObjects.put(beanName, singletonObject);
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

二.createBean()

					//方法1 getSingleton()
					sharedInstance = getSingleton(beanName, () -> {
						try {
						//方法2 createBean()
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							destroySingleton(beanName);
							throw ex;
						}
					});

1.getSingleton()

   //下边代码只留主要的了
	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		synchronized (this.singletonObjects) {
		   //从一级缓存取
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
			 //加入到 正在创建Bean的集合(Set<String> singletonsCurrentlyInCreation)
				beforeSingletonCreation(beanName);
				try {
				   //此处执行getObject()方法,实际执行的是createBean(beanName, mbd, args)方法
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
				}
				catch (BeanCreationException ex) {
				}
				finally {
			//从正在创建Bean的集合(Set<String> singletonsCurrentlyInCreation)移除
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
				   //1.加入一级缓存
				   //2.从二级缓存移除
				   //1.从三级缓存移除
				   //1.加入registeredSingletons集合中
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

2.doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)

 protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
 		throws BeanCreationException {

 	// Instantiate the bean.
 	BeanWrapper instanceWrapper = null;
 	if (mbd.isSingleton()) {
 		instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
 	}
 	if (instanceWrapper == null) {
 		instanceWrapper = createBeanInstance(beanName, mbd, args);
 	}
 	Object bean = instanceWrapper.getWrappedInstance();
 	Class<?> beanType = instanceWrapper.getWrappedClass();
 	if (beanType != NullBean.class) {
 		mbd.resolvedTargetType = beanType;
 	}
     //此处加入三级缓存,<beanName,ObjectFactory>,是不完整的bean,提前曝光防止循环依赖
     //如果没有aop等,可以不用三级缓存,如果有aop需要三级缓存,因为getEarlyBeanReference()方法执行了前后置处理器,是代理bean。
 	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
 			isSingletonCurrentlyInCreation(beanName));
 	if (earlySingletonExposure) {
 		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
 	}

 	// Initialize the bean instance.
 	Object exposedObject = bean;
 	try {
 	   //注入bean的属性
 		populateBean(beanName, mbd, instanceWrapper);
 		//初始化bean,初始化前后执行后置处理器
 		exposedObject = initializeBean(beanName, exposedObject, mbd);
 	}
 	catch (Throwable ex) {
 	}
 	try {
 		registerDisposableBeanIfNecessary(beanName, bean, mbd);
 	}
 	catch (BeanDefinitionValidationException ex) {
 	}
 	return exposedObject;
 }

2-1 addSingletonFactory() 把objectFactory加入三级缓存

 protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
 	synchronized (this.singletonObjects) {
 		if (!this.singletonObjects.containsKey(beanName)) {
 	    	//放入三级缓存
 			this.singletonFactories.put(beanName, singletonFactory);
 			this.earlySingletonObjects.remove(beanName);
 			this.registeredSingletons.add(beanName);
 		}
 	}
 }

2-2 populateBean(beanName, mbd, instanceWrapper)

注入bean的属性,各种类型的属性注入,list,map,bean等,一步步跟进
populateBean(beanName, mbd, instanceWrapper)-->
applyPropertyValues(beanName, mbd, bw, pvs)-->
valueResolver.resolveValueIfNecessary(pv, originalValue) 
//各种类型的属性注入,list,map,bean等
 public Object resolveValueIfNecessary(Object argName, @Nullable Object value) {
    //如果注入的是bean A,会去getBean(A)
 	if (value instanceof RuntimeBeanReference) {
 		RuntimeBeanReference ref = (RuntimeBeanReference) value;
 		return resolveReference(argName, ref);
 	}
 	else if (value instanceof RuntimeBeanNameReference) {
 		String refName = ((RuntimeBeanNameReference) value).getBeanName();
 		refName = String.valueOf(doEvaluate(refName));
 		if (!this.beanFactory.containsBean(refName)) {
 			throw new BeanDefinitionStoreException(
 					"Invalid bean name '" + refName + "' in bean reference for " + argName);
 		}
 		return refName;
 	}
 	else if (value instanceof BeanDefinitionHolder) {
 		// Resolve BeanDefinitionHolder: contains BeanDefinition with name and aliases.
 		BeanDefinitionHolder bdHolder = (BeanDefinitionHolder) value;
 		return resolveInnerBean(argName, bdHolder.getBeanName(), bdHolder.getBeanDefinition());
 	}
 	else if (value instanceof BeanDefinition) {
 		// Resolve plain BeanDefinition, without contained name: use dummy name.
 		BeanDefinition bd = (BeanDefinition) value;
 		String innerBeanName = "(inner bean)" + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR +
 				ObjectUtils.getIdentityHexString(bd);
 		return resolveInnerBean(argName, innerBeanName, bd);
 	}
 	else if (value instanceof DependencyDescriptor) {
 		Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
 		Object result = this.beanFactory.resolveDependency(
 				(DependencyDescriptor) value, this.beanName, autowiredBeanNames, this.typeConverter);
 		for (String autowiredBeanName : autowiredBeanNames) {
 			if (this.beanFactory.containsBean(autowiredBeanName)) {
 				this.beanFactory.registerDependentBean(autowiredBeanName, this.beanName);
 			}
 		}
 		return result;
 	}
 	else if (value instanceof ManagedArray) {
 		// May need to resolve contained runtime references.
 		ManagedArray array = (ManagedArray) value;
 		Class<?> elementType = array.resolvedElementType;
 		if (elementType == null) {
 			String elementTypeName = array.getElementTypeName();
 			if (StringUtils.hasText(elementTypeName)) {
 				try {
 					elementType = ClassUtils.forName(elementTypeName, this.beanFactory.getBeanClassLoader());
 					array.resolvedElementType = elementType;
 				}
 				catch (Throwable ex) {
 					// Improve the message by showing the context.
 					throw new BeanCreationException(
 							this.beanDefinition.getResourceDescription(), this.beanName,
 							"Error resolving array type for " + argName, ex);
 				}
 			}
 			else {
 				elementType = Object.class;
 			}
 		}
 		return resolveManagedArray(argName, (List<?>) value, elementType);
 	}
 	else if (value instanceof ManagedList) {
 		// May need to resolve contained runtime references.
 		return resolveManagedList(argName, (List<?>) value);
 	}
 	else if (value instanceof ManagedSet) {
 		// May need to resolve contained runtime references.
 		return resolveManagedSet(argName, (Set<?>) value);
 	}
 	else if (value instanceof ManagedMap) {
 		// May need to resolve contained runtime references.
 		return resolveManagedMap(argName, (Map<?, ?>) value);
 	}
 	else if (value instanceof ManagedProperties) {
 		Properties original = (Properties) value;
 		Properties copy = new Properties();
 		original.forEach((propKey, propValue) -> {
 			if (propKey instanceof TypedStringValue) {
 				propKey = evaluate((TypedStringValue) propKey);
 			}
 			if (propValue instanceof TypedStringValue) {
 				propValue = evaluate((TypedStringValue) propValue);
 			}
 			if (propKey == null || propValue == null) {
 				throw new BeanCreationException(
 						this.beanDefinition.getResourceDescription(), this.beanName,
 						"Error converting Properties key/value pair for " + argName + ": resolved to null");
 			}
 			copy.put(propKey, propValue);
 		});
 		return copy;
 	}
 	else if (value instanceof TypedStringValue) {
 		// Convert value to target type here.
 		TypedStringValue typedStringValue = (TypedStringValue) value;
 		Object valueObject = evaluate(typedStringValue);
 		try {
 			Class<?> resolvedTargetType = resolveTargetType(typedStringValue);
 			if (resolvedTargetType != null) {
 				return this.typeConverter.convertIfNecessary(valueObject, resolvedTargetType);
 			}
 			else {
 				return valueObject;
 			}
 		}
 		catch (Throwable ex) {
 			// Improve the message by showing the context.
 			throw new BeanCreationException(
 					this.beanDefinition.getResourceDescription(), this.beanName,
 					"Error converting typed String value for " + argName, ex);
 		}
 	}
 	else if (value instanceof NullBean) {
 		return null;
 	}
 	else {
 		return evaluate(value);
 	}
 }

简单分析一下方法resolveReference(argName, ref)

 private Object resolveReference(Object argName, RuntimeBeanReference ref) {
 	try {
 		Object bean;
 		Class<?> beanType = ref.getBeanType();
 		if (ref.isToParent()) {
 			BeanFactory parent = this.beanFactory.getParentBeanFactory();
 			if (parent == null) {
 			}
 			if (beanType != null) {
 				bean = parent.getBean(beanType);
 			}
 			else {
 				bean = parent.getBean(String.valueOf(doEvaluate(ref.getBeanName())));
 			}
 		}
 		else {
 			String resolvedName;
 			if (beanType != null) {
 				NamedBeanHolder<?> namedBean = this.beanFactory.resolveNamedBean(beanType);
 				bean = namedBean.getBeanInstance();
 				resolvedName = namedBean.getBeanName();
 			}
 			else {
 				resolvedName = String.valueOf(doEvaluate(ref.getBeanName()));
 				//这里geanBean()
 				bean = this.beanFactory.getBean(resolvedName);
 			}
 			this.beanFactory.registerDependentBean(resolvedName, this.beanName);
 		}
 		if (bean instanceof NullBean) {
 			bean = null;
 		}
 		return bean;
 	}
 	catch (BeansException ex) {
 }

2-3 initializeBean(beanName, exposedObject, mbd)初始化bean

    这里会执行实现了aware接口的类,和BeanPostProcessor 初始化前后执行后置处理器
 protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
 	if (System.getSecurityManager() != null) {
 		AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
 			invokeAwareMethods(beanName, bean);
 			return null;
 		}, getAccessControlContext());
 	}
 	else {
 	   //调用实现了BeanNameAware,BeanClassLoaderAware,BeanFactoryAware等接口的方法
 	   //下面有分析
 		invokeAwareMethods(beanName, bean);
 	}
 	Object wrappedBean = bean;
 	if (mbd == null || !mbd.isSynthetic()) {
 	//执行后置处理器
 		wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
 	}
 	try {
 	   //下面有分析
 		invokeInitMethods(beanName, wrappedBean, mbd);
 	}
 	catch (Throwable ex) {
 	}
 	if (mbd == null || !mbd.isSynthetic()) {
 			//执行后置处理器
 		wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
 	}
 	return wrappedBean;
 }

简单解析一下invokeAwareMethods(String beanName, Object bean)

	private void invokeAwareMethods(String beanName, Object bean) {
 	if (bean instanceof Aware) {
 		if (bean instanceof BeanNameAware) {
 			((BeanNameAware) bean).setBeanName(beanName);
 		}
 		if (bean instanceof BeanClassLoaderAware) {
 			ClassLoader bcl = getBeanClassLoader();
 			if (bcl != null) {
 				((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
 			}
 		}
 		if (bean instanceof BeanFactoryAware) {
 			((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
 		}
 	}
 }

简单解析一下invokeInitMethods(beanName, wrappedBean, mbd);

 	protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
 		throws Throwable {
    // 首先会检查是否是 InitializingBean ,如果是的话需要调用 afterPropertiesSet()
 	boolean isInitializingBean = (bean instanceof InitializingBean);
 	if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
 		if (logger.isTraceEnabled()) {
 			logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
 		}
 		if (System.getSecurityManager() != null) {
 			try {
 				AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
 				   //调用实现InitializingBean接口的afterPropertiesSet()方法
 					((InitializingBean) bean).afterPropertiesSet();
 					return null;
 				}, getAccessControlContext());
 			}
 			catch (PrivilegedActionException pae) {
 				throw pae.getException();
 			}
 		}
 		else {
 		  //调用实现InitializingBean接口的afterPropertiesSet()方法
 			((InitializingBean) bean).afterPropertiesSet();
 		}
 	}
 	if (mbd != null && bean.getClass() != NullBean.class) {
 	// 判断是否指定了 init-method(),
     // 如果指定了 init-method(),则再调用制定的init-method
 		String initMethodName = mbd.getInitMethodName();
 		if (StringUtils.hasLength(initMethodName) &&
 				!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
 				!mbd.isExternallyManagedInitMethod(initMethodName)) {
 				// 激活用户自定义的初始化方法
                 // 利用反射机制执行
 			invokeCustomInitMethod(beanName, bean, mbd);
 		}
 	}
 }
先调用afterPropertiesSet()方法,后反射init-method方法
//Spring 在完成实例化后,设置完所有属性,进行 “Aware 接口” 和 “BeanPostProcessor 前置处理”之后,会接着检测当前 bean 对象是否实现了 InitializingBean 接口。如果是,则会调用其 #afterPropertiesSet() 方法,进一步调整 bean 实例对象的状态。
  public interface InitializingBean {
     void afterPropertiesSet() throws Exception;
  }

示例

public class InitializingBeanTest implements InitializingBean {
 private String name;
 @Override
 public void afterPropertiesSet() throws Exception {
     System.out.println("InitializingBeanTest initializing...");
     this.name = "中路杀神";
 }
 public String getName() {
     return name;
 }
 public void setName(String name) {
     this.name = name;
 }
}

配置项如下

<bean id="initializingBeanTest" class="org.springframework.core.test.InitializingBeanTest">
 <property name="name" value="大西瓜"/>
</bean>

测试代码

InitializingBeanTest test = (InitializingBeanTest) factory.getBean("initializingBeanTest");
System.out.println("name :" + test.getName());

输出结果

InitializingBeanTest initializing...
中路杀神
<think> 我们正在讨论Spring框架中如何通过bean名称注入bean对象。根据引用内容,我们可以总结几种方法: 1. 使用`@Resource`注解:默认按名称注入(引用[2]指出:@Resource默认按照名称进行装配,名称可以通过name属性指定) 2. 使用`@Autowired`配合`@Qualifier`:通过指定bean名称注入 3. 使用`ApplicationContext`的`getBean`方法:通过bean名称获取(引用[1]中示例:`context.getBean("user1", User.class)`) 下面详细说明每种方法: ### 方法1:使用`@Resource`注解按名称注入 `@Resource`是JavaEE标准注解,Spring也支持。它默认按bean名称注入,也可以指定名称。 ```java import javax.annotation.Resource; public class MyService { @Resource(name = "myBean") // 按名称"myBean"注入 private MyBean bean; } ``` - 如果未指定name属性,则默认使用字段名作为bean名称查找(引用[2])。 - 例如,字段名为`user1`,则会查找名为`user1`的bean。 ### 方法2:使用`@Autowired`配合`@Qualifier` `@Autowired`是Spring注解,默认按类型注入。结合`@Qualifier`可以按名称注入。 ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; public class MyService { @Autowired @Qualifier("myBean") // 指定bean名称 private MyBean bean; } ``` ### 方法3:使用`ApplicationContext`直接获取 在代码中通过`ApplicationContext`的`getBean`方法,传入bean名称获取。 ```java import org.springframework.context.ApplicationContext; public class MyService { private MyBean bean; public MyService(ApplicationContext context) { this.bean = context.getBean("myBean", MyBean.class); } } ``` 或者使用`BeanFactory`(不推荐在业务代码中直接使用,但了解其存在): ```java import org.springframework.beans.factory.BeanFactory; public class MyService { private MyBean bean; public MyService(BeanFactory beanFactory) { this.bean = beanFactory.getBean("myBean", MyBean.class); } } ``` 注意:直接使用`getBean`会引入Spring依赖,降低可测试性,应优先考虑前两种方法。 ### 注意事项 1. **bean命名规则**: - 默认情况下,Spring将类名首字母小写作为bean名称(如`UserService`的bean名称为`userService`)。 - 使用`@Bean`注解时,方法名即为bean名称(引用[1]中示例:`@Bean`方法名为`user1`,则bean名称为`user1`)。 - 可以显式指定名称:`@Component("myBean")`或`@Bean(name="myBean")`。 2. **名称冲突**: - 同一Spring上下文中不能有两个相同名称的bean,否则会报`BeanDefinitionStoreException`。 3. **按名称注入与按类型注入的选择**: - 当一个接口有多个实现类时,必须使用按名称注入(或按限定符)来消除歧义。 ### 示例场景 假设有一个接口`MessageService`和两个实现类: ```java public interface MessageService { String getMessage(); } @Service("emailService") public class EmailService implements MessageService { @Override public String getMessage() { return "Email message"; } } @Service("smsService") public class SmsService implements MessageService { @Override public String getMessage() { return "SMS message"; } } ``` 在客户端类中按名称注入: ```java @Component public class MyClient { @Resource(name = "emailService") private MessageService service; // 注入EmailService实例 // 或者使用@Qualifier /* @Autowired @Qualifier("smsService") private MessageService anotherService; */ } ``` ### 总结 Spring通过bean名称注入bean对象主要有三种方式: 1. **@Resource(name="beanName")**:最简洁,推荐使用(但注意:JavaEE标准,非Spring特有)。 2. **@Autowired + @Qualifier("beanName")**:纯Spring方式。 3. **ApplicationContext.getBean("beanName")**:编程式获取,灵活但需谨慎使用。 根据项目规范选择合适方式,通常推荐注解注入(前两种)以提高代码可读性和可维护性[^1][^2]。 §§相关问题§§ 1. Spring中如何解决多个同类型bean的冲突问题? 2. 如何自定义Spring bean的名称生成策略? 3. Spring的`@Resource`和`@Autowired`注解有什么区别? </think> 在Spring框架中,通过bean名称注入bean对象是依赖注入的核心功能之一。以下是三种主要实现方式及其原理说明: ### 一、使用 `@Resource` 注解(推荐) **原理**:`@Resource` 是JSR-250标准注解,默认按**bean名称**进行注入[^2] ```java @Component public class OrderService { // 1. 按字段名注入(字段名=bean名称) @Resource private UserService userService; // 注入名为"userService"的bean // 2. 显式指定bean名称 @Resource(name = "paymentServiceV2") private PaymentService payment; } ``` **特点**: 1. 未指定name时,默认使用**字段名**作为bean名称查找 2. 指定name后,强制按名称注入 3. 名称匹配失败时才尝试按类型注入[^2] --- ### 二、使用 `@Autowired` + `@Qualifier` 组合 **原理**:`@Autowired`默认按类型注入,配合`@Qualifier`实现按名称注入 ```java @Service public class ProductService { // 组合注解实现按名称注入 @Autowired @Qualifier("inventoryDaoImpl") // 指定bean名称 private InventoryDao inventoryDao; } ``` **特点**: 1. `@Qualifier`显式定义目标bean名称 2. 适用于需要精确指定特定实现的场景 --- ### 三、通过 `ApplicationContext` 编程式获取 **原理**:直接从Spring容器中按名称获取bean[^1] ```java @Service public class ReportService { @Autowired private ApplicationContext context; public void generateReport() { // 通过名称获取bean User user = context.getBean("user1", User.class); // 使用bean... } } ``` **特点**: 1. 需先注入 `ApplicationContext` 2. `getBean("beanName", Class)` 方法需指定名称和类型 3. 适用于动态获取bean的场景 --- ### ⚠️ 关键注意事项 1. **Bean命名规则**: - 默认命名策略:类名首字母小写(如 `UserService` → `"userService"`) - 自定义名称:`@Component("myBean")` 或 `@Bean(name="customName")`[^1] 2. **名称冲突处理**: ```java @Configuration public class AppConfig { @Bean(name = "dataSource") public DataSource primaryDataSource() { ... } // 同名bean会覆盖 } ``` 3. **最佳实践**: - 优先使用 `@Resource` 按名称注入 - 避免直接使用 `ApplicationContext.getBean()`(破坏IoC原则) - 多实现类场景务必指定名称: ```java @Service public class PaymentServiceV1 implements PaymentService { ... } @Service public class PaymentServiceV2 implements PaymentService { ... } // 使用处 @Resource(name = "paymentServiceV2") private PaymentService paymentService; ``` --- ### 完整示例 ```java // 定义Bean @Configuration public class Config { @Bean(name = "user1") // 自定义bean名称 public User createUser() { return new User("Admin"); } } // 注入使用 @Service public class AuthService { @Resource(name = "user1") // 按名称注入 private User adminUser; } ``` > **故障排查**:若注入失败,检查: > 1. Bean是否被Spring管理(有 `@Component` 等注解) > 2. 名称拼写是否一致(包括大小写) > 3. 是否存在多个同类型bean未指定名称
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_当i已成往事

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值