源码阅读---Spring依赖注入

Spring源码解读–依赖注入

1 Spring的注入方式

摘要:

(1)Spring的注入方式其实可以分为手动注入自动注入两种方式:

(2)一半基于xml文件配置实现手动注入;自动注入可以基于xml或者注解方式实现自动注入;

(3) 手动注入需要继续setter或者构造方法注入,自动注入则可以基于setter或者构造方法或者属性(注解方式)或者普通方法(注解方式)

1.1 手动注入

1.1.1 xml 配置中 property属性手动注入(setter方法注入)

注意:

使用property的方式注入必须有setter方法才能注入成功

<bean id="orderService" name="orderService" class="show.mrkay.service.OrderService"/>
<bean id="userService" name="userService" class="show.mrkay.service.UserService">
	<!--xml手动注入方式: 这种注入方式在UserService中必须要存在set方法才能将其注入进去-->
	<property name="orderService" ref="orderService"/>
</bean>
public class UserService {
	private OrderService orderService;

	/**
	 * xml中配置了手动注入之后这里必须提供setter方法来完成手动注入
	 *
	 * @param orderService
	 */
	public void setOrderService(OrderService orderService) {
		this.orderService = orderService;
	}

	public void show() {
		orderService.orderShow("-------UserService------>orderService.orderShow");
	}
}
public class BeanAutowaredClient {
	public static void main(String[] args) {
		ApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
		UserService userService = app.getBean("userService", UserService.class);
		userService.show();
	}
}
1.1.2 xml 构造方法手动注入(构造方法注入)

构造方法注入必须要有构造方法

<bean id="orderService" name="orderService" class="show.mrkay.service.OrderService"/>
<bean id="userService" name="userService" class="show.mrkay.service.UserService">
	<constructor-arg ref="orderService" index="0"/>
</bean>
public class UserService {
	private OrderService orderService;

	/**
	 * setter方法来完成手动注入
	 *
	 * @param orderService orderService属性
	 */
	public void setOrderService(OrderService orderService) {
		this.orderService = orderService;
	}

	/**
	 * 构造方法完成手动注入
	 *
	 * @param orderService orderService属性
	 */
	public UserService(OrderService orderService) {
		this.orderService = orderService;
	}

	public void show() {
		orderService.orderShow("-------UserService------>orderService.orderShow");
	}
}
public class BeanAutowaredClient {
	public static void main(String[] args) {
		ApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
		UserService userService = app.getBean("userService", UserService.class);
		userService.show();
	}
}

1.2 自动注入

xml自动注入:

​ (1) 基于set方法自动注入

​ (2) 基于构造方法自动注入

注解@Autowired注解自动注入

​ (1) 基于属性自动注入

​ (2) 基于构造方法自动注入

​ (3)基于方法自动注入

1.2.1 xml自动注入方式

摘要:

xml自动注入需要提供setter方法或者构造方法;

xml自动注入分为byName,byType,constructor,方式

xml中配置如下

<!--使用xml的方式注入实例到IOC容器-->
<bean id="orderService" name="orderService" class="show.mrkay.service.OrderService"/>
<bean id="orderService1" name="orderService1" class="show.mrkay.service.OrderService"/>

<!--xml自动注入: byName,必须要由setter方法-->
<bean id="userService" name="userService" class="show.mrkay.service.UserService" autowire="byName"/>

<!--xml根据类型自动注入,必须要有setter方法-->
<bean id="userService" name="userService" class="show.mrkay.service.UserService" autowire="byType"/>

<!--xml基于构造方法实现自动注入,必须要有构造方法-->
<bean id="userService" name="userService" class="show.mrkay.service.UserService" autowire="constructor"/>

<!--xml默认方式实现(默认是no,不进行自动注入)-->
<bean id="userService" name="userService" class="show.mrkay.service.UserService" autowire="default"/>

java中需要代码如下

public class UserService {
	private OrderService orderService;

	/**
	 * xml:setter方法,byName byType需要提供此方法
	 *
	 * @param orderService orderService属性
	 */
	public void setOrderService(OrderService orderService) {
		this.orderService = orderService;
	}

	/**
	 * xml:构造方法完成手动注入,自动注入constructor需要提供此方法
	 *
	 * @param orderService orderService属性
	 */
	public UserService(OrderService orderService) {
		 this.orderService = orderService;
	}

	public void show() {
		orderService.orderShow("-------UserService------>orderService.orderShow");
	}
}

分析:

1 当我们我们byType方式和byName方式实现自动注入时需要提供setter方法;

2 当我们配置了两个类型的OrderService对象的时候使用byType方式实现自动注入会报错;

3 当我们使用constructor方式实现自动注入时需要提供构造方法;

4 不管根据 byType,byName还是constructor方式都是使用的方法参数中属性名和类型,与全局属性(private OrderService orderService)无关,

​ 也就是即使不写private OrderService orderService; 照样不会报错;setter方法或者构造方法仍然正常执行,

​ 但是这样做一般没有意义;

5 使用byType当我们注入的时候,会根据参数中的OrderService类型在Spring容器中进行查找,因为我们上面配置了两个,所以Spring会找到两个OrderService,但是不知道注入哪一个,所以会抛出异常;(第2步报错原因);

6 当我们使用byName方式的时候就会根据方法中参数的名字进行查找;

7 我们的default属性其实就是no,不在xml中使用自动注入,Spring这样做的原因是为了代码阅读起来更加容易,能够很好的分辨Bean和对象;

​ 比如: 如果我们使用了xml方式的自动注入,这是用我们OrderService需要注入,但是我们在UserrService中需要一个Student对象,但是该对象并不是Spring的Bean这时候,如果代码比较多了,再看代码的时候我们就很难区分哪个事Bean对象注入,哪一个事普通对象;

1.2.2 @Autoawired注解自动注入

摘要:

在Spring组件成熟之后,越来越多的注解体替代了xml方式的配置;

很显然我们依赖注入方式也有注解可以替代这些xml配置

正如我们自动注入,Spring就提供了@Autowired注解来实现自动注入,并在此注解上对自动注入进行了优化

@Autowired注解的自动注入实现了优化处理,此注解不仅仅能添加到属性上,还可以要添加到普通方法上(包括setter方法),以及构造函数上

我们下面使用最常用的属性方式来是演示效果;

注解方式配置文件,xml方式被替代

@Configuration
@ComponentScan("show.mrkay")
public class AppConfig {

	/**
	 * 模拟容其中实例化两个Bean对象
	 *
	 * @param
	 * @MethodName: OrderService
	 * @return: show.mrkay.service.OrderService
	 */
	@Bean
	public OrderService OrderService() {
		return new OrderService();
	}

	/**
	 * 模拟容其中实例化两个Bean对象
	 *
	 * @param
	 * @MethodName: OrderService1
	 * @return: show.mrkay.service.OrderService
	 */
	@Bean
	public OrderService OrderService1() {
		return new OrderService();
	}
}

注解方式创建Bean

@Service
public class UserService {
   /**
	 *属性方式是实现@Autowired注解自当注入
	 *
	 * @param orderService
	 * @MethodName: UserService
	 * @return:
	 */
   private OrderService orderService;
	/**
	//  * 构造方式是实现@Autowired注解自当注入
	//  *
	//  * @param orderService
	//  * @MethodName: UserService
	//  * @return:
	//  */
	// @Autowired
	// public UserService(OrderService orderService) {
	// 	this.orderService = orderService;
	// }
	//
	// /**
	//  * setter方式是实现@Autowired注解自当注入
	//  *
	//  * @param orderService
	//  * @MethodName: setOrderService
	//  * @return: void
	//  */
	// @Autowired
	// public void setOrderService(OrderService orderService) {
	// 	this.orderService = orderService;
	// }
	//
	// /**
	//  * 普通方式是实现@Autowired注解自当注入
	//  *
	//  * @param orderService
	//  * @param orderService1
	//  * @MethodName: diFiled
	//  * @return:
	//  */
	// @Autowired
	// public void diFiled(OrderService orderService, OrderService orderService1) {
	// 	this.orderService = orderService;
	// }
	public void show() {
		orderService.orderShow("-------UserService------>orderService.orderShow");
	}
}
@Service
public class OrderService {
	private String username;

	public String orderShow(String string) {
		System.out.println(string);
		return "success-kay";
	}
}

注解方式测试类

public class DiClientTest {
	public static void main(String[] args) {
		//xml配置方式context
		// ApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
		//注解配置方式context
		ApplicationContext app = new AnnotationConfigApplicationContext(AppConfig.class);
		UserService userService = app.getBean("userService", UserService.class);
		userService.show();
	}
}

演示一下效果(注入成功)

在这里插入图片描述

@Autowired底层实现原理步骤:

1 我们每一个@Autowired注解其实就是一个注入点(这里再源码中会涉及注解后置处理器后面会 详细介绍);

2 Spring扫描所有的注入点,当扫描到注解之后,首先会根据类型去容其中找要注入的Bean如果没有就报错,如果有且只有一个就直接注入,如果找到多个就再根据name去寻找具体的Bean完成注入;

3 不管是属性方式注入,还是构造方式或者普通方法都是根据byType之后byName查找需要注入的Bean;

4 我们再上面代码中看到在方法注入中参数可以传递多个,这样也是能够执行的,但是Spring会依次将查找所有的参数对象的类型和名字对象的Bean,过程和单个参数一样;比如当第一个参数orderService根据type和name查找完毕之后,就会根据第二个参数的type和name再去查找,过程跟单个一毛一样!!!

1.3 @Autowired 自动注入源码

上面代码已经大致了解了Spring的依赖注入流程

大致流程虽然是清楚了但是有很多细节的地方其实并不是那么简单,Spring的依赖注入还是很巧妙的;

经过这两天的学习以及网上大神的讲解我这里重新整理了一下源码

想要阅读并运行源码且在源码上写注释,这里需要提前准备几点:

1 安装Gradle并配置环境(Gradle版本需要和你阅读源码的Gradle版本保持一致)

2 下载Spring源码,并导入

3 在Spring中创建自己的模块

友情连接:

[https://blog.csdn.net/weixin_44848900/article/details/115436312?spm=1001.2014.3001.5502]:

1.3.1 相关其他注解

这里介绍一些我们常用和不常用的注解,便于在后面理解源码更加容易;

1.3.1.1 @Autowired

这个注解是依赖注入最常用的方法,但是你可能没有用过它的一些required属性;

@Autowired(required = false)//要注入到属性中的Bean可以为null
@Autowired(required = true)//要注入到属性中的Bean不能为null
@Autowired //required = true默认为true
/*
也就是说我们在自动注入的时候,要注入到属性orderService中的Bean能不能为null
要看这个属性,如果配置了可以为null,即使查找到的Bean即使为null也不会报错;
*/
1.3.1.2 @Value

1 @Value(“xxx”)

/*
第一种:
需要我们自定义类型转换器来将String类型转换成OrderService类型,Spring在扫描到这个注解的时候,
会拿到这个注解中的"test-value"然后执行我们自定义的类型转换器来将"test-value"转换成OrderService类型
没有自定义转换器,类型转换就会抛出异常;(String和Object类型除外,因为可以自己转换)
*/
@Value("test-value")
private String username;
@Value("test-service")
private Object testObject;

在这里插入图片描述

在这里插入图片描述

2 @Value("${xxx}")

/*
第二种:
这种方式是我们在Spring种最常用的注解方法,就是使用$符号来做占位符,
我们一般用来将.properties配置文件中的值来填充占位符
下面代码看一下就能明白,这里就不在代码中运行演示了.
*/
@Value("${jdbc.driver}")
private String driver;

3 @Value("#{xxx}")

/*
第三种:
这种方式是EL表达式方式,Spring扫描到注解种的EL表达式,就会去解析执行得到执行的结果就能使用了;
EL表达式可以是一个对象的属性也可以是一个三目运算符等;
*/
@Value("#{orderService.username}")
private String username;
1.3.1.3 @Lazy

此注解的value属性有两个值可以选

@Lazy(value=true)//默认值就是True

@Lazy(value=false)//不加@Lazy注解与此同效

/*
见名知意,就是懒加载的意思,如果我们不加Spring默认会在容器启动的时候帮我们创建一遍对象放到IOC容器中;
如果我们加了@Lazy注解就等于只有在使用到这个Bean的时候Spring才会去创建它;
*/
1.3.1.4 @Primary

此注解表示声明哪一个是主要的Bean,如果Spring根据类型找到了两个Bean会去判断哪一个主Bean,判断的一句就是根据这样一个注解来判断的

此注解可以加在方法上,具体可看源码,我就不再例举了

@Bean
@Primary  //此注解表示声明这是一个主Bean
public OrderService orderService() {
	return new OrderService();
}
1.3.1.5 @Priority

此注解用于声明对象的优先级;

需要注意的是此诸结只能声明在类上面,不能使用在方法或者属性上面;

源码如下:

value值越小优先级越高

此注解不是Spring的注解而是 javax.annotation包下的注解

@Target({ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Priority {
    int value();
}
1.3.2 自动依赖注入源码流程

如果前面的准备工作都已经完成了,到了这里,对于我们阅读Spring依赖注入的源码就相对不会这么难了;

当然如果想要更好的理解Spring源码,最好能事先温习一下Java的基础部分特别是

注解反射部分我们应该有一个基本的认识,明白反射的原理和一些元注解的作用,以及如何定义我们自己的注解

设计模式如果能了解一下会更好理解我们的源码,因为Spring源码使用了很多设计模式,封装的比较深,阅读起来会很费劲;

参考连接:注解和反射
好了废话不多说,开始我们的"正菜".

1.3.2.1 寻找注入点

每一个@Autowired注解都是一个注入点;

Spring会去扫描包中注解,根据@Autowired来获取我们注入点的相关信息便于后期属性的注入;

注意:

下面代码板书顺序就是流程,并且具体的细节都在注释中,所以这里不再一一总结,

如果有难点会进行总结,没有难点的话直接看注释即可,文末会有详细的流程图供后期参考;

1 首先/容器启动后Spring会调用专门用于依赖注入的后置处理器 来处理依赖注入的问题

//AutowiredAnnotationBeanPostProcessor,java

2 接着扫描所有的@Autowired注解获取出所有的注入点

/**
 * 此方法用于寻找注入点--lk(我们的依赖@Autowired注解就是依靠这个注解完成的扫描和注入)
 *
 * @param beanDefinition Bean的定义
 * @param beanType       Bean的类型(如:UserService)
 * @param beanName       Bean的名字(如:userService)
 * @MethodName: postProcessMergedBeanDefinition
 * @return: void
 */
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
	//InjectionMetadata对象表示:类的所有注入点的集合(用于管理所有的注入点)--lk,此方法用于寻找所有的注入点
	InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
	metadata.checkConfigMembers(beanDefinition);
}

3 进入findAutowiringMetadata(beanName, beanType, null)去寻找注入点

/**
 * 寻找注入点--lk
 *
 * @param beanName
 * @param clazz
 * @param pvs
 * @MethodName: findAutowiringMetadata
 * @return: org.springframework.beans.factory.annotation.InjectionMetadata
 */
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
		// Fall back to class name as cache key, for backwards compatibility with custom callers.
		String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
		// Quick check on the concurrent map first, with minimal locking.
		//先从缓存中寻找
		InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
		if (InjectionMetadata.needsRefresh(metadata, clazz)) {
			synchronized (this.injectionMetadataCache) {
				metadata = this.injectionMetadataCache.get(cacheKey);
				if (InjectionMetadata.needsRefresh(metadata, clazz)) {
					if (metadata != null) {
						metadata.clear(pvs);
					}
					//真正开始寻找注入点
					metadata = buildAutowiringMetadata(clazz);
					//找到缓存一下
					this.injectionMetadataCache.put(cacheKey, metadata);
				}
			}
		}
		return metadata;
	}

4 上一步寻找注入点的方式主要是对找到的注入点进行缓存,下次使用的时候直接从缓存中查找更加方便,

而buildAutowiringMetadata(clazz); 方法才是真正的寻找注入点的逻辑

/**
 * 寻找注入点--lk
 * 介绍一下几个对象:
 * InjectionMetadata: 该对象表示类中(如:UserService)的所有注入点
 * InjectedElement: 此对象表示所有注入点中的一个,他是InjectionMetadata对象中的一个静态类,包括属性注入点和方法注入点/n
 * AutowiredFieldElement:InjectedElement的子类,专门用于记录属性注入点的信息;
 * AutowiredMethodElement:也是InjectedElement的子类,专门用记录方法注入点的信息;
 *
 * @param clazz
 * @MethodName: buildAutowiringMetadata
 * @return: org.springframework.beans.factory.annotation.InjectionMetadata
 */
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
		List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
		Class<?> targetClass = clazz;

		do {
			final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
			//遍历所有属性(属性注入会走),看是否有@Autowired或者@Value或者@Inject注解
			ReflectionUtils.doWithLocalFields(targetClass, field -> {
				//寻找属性字段上是否存在注入注解
				AnnotationAttributes ann = findAutowiredAnnotation(field);
				if (ann != null) {//存在
					//判断注入的属性是否是static修饰的,是的话直接return不进行添加
					if (Modifier.isStatic(field.getModifiers())) {
						if (logger.isInfoEnabled()) {
							logger.info("Autowired annotation is not supported on static fields: " + field);
						}
						return;
					}
					//获取@Autowired注解中的required属性@Autowired(required = true)默认是为true的
					//required为true的时候注入的对象不能为null的,当为false时表示可以为空,但是没有意义
					boolean required = determineRequiredStatus(ann);
					//将此属性注入点添加到集合中
					//可以看到使用的是AutowiredFieldElement是表示专门记录的属性的注入点
					currElements.add(new AutowiredFieldElement(field, required));
				}
			});

			//遍历所有方法(方法注入会走),看是否有@Autowired或者@Value或者@Inject注解
			ReflectionUtils.doWithLocalMethods(targetClass, method -> {
				Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
				if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
					return;
				}
				//查看方法上是否存在@Autowired注解
				AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
				//存在注解进入分支内部
				if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
					//方法为静态的,直接打印日志,不去处理,然后返回
					if (Modifier.isStatic(method.getModifiers())) {
						if (logger.isInfoEnabled()) {
							logger.info("Autowired annotation is not supported on static methods: " + method);
						}
						return;
					}
					//判断方法中参数个数是否为0
					if (method.getParameterCount() == 0) {
						//没有参数直接打印日志,没有擦拭农户注入没有意义
						if (logger.isInfoEnabled()) {
							logger.info("Autowired annotation should only be used on methods with parameters: " + method);
						}
					}
					//获取required属性值
					boolean required = determineRequiredStatus(ann);
					//根据方法找出对应的属性
					PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
					//将方法注入点添加到注入点中;
					currElements.add(new AutowiredMethodElement(method, required, pd));
				}
			});
			//将上面遍历的所单个注入点添加到集合
			elements.addAll(0, currElements);
			//查看父类
			targetClass = targetClass.getSuperclass();
		} while (targetClass != null && targetClass != Object.class);
		//返回类中所有注入点的集合
		return new InjectionMetadata(clazz, elements);
	}
1.3.2.2 根据注入点注入属性

获取出所有的注入点之后会根据注入点去查找Bean对象注入到我们的属性中

1 同样执行属性的注入还是会在同样的后置处理器中执行属性注入方法

//AutowiredAnnotationBeanPostProcessor.java

2 在注入点遍历完成之后会去调用该属性注入的方法去完成属性的注入

	/**
	 * 根据寻找的来的注入点注入属性--lk
	 *
	 * @param pvs      属性值
	 * @param bean     我们的Bean(如果要在UserService中注入OrderService这里的bean就是UserService)
	 * @param beanName 如果要在UserService中注入OrderService这里的beanName就是userService
	 * @MethodName: postProcessProperties
	 * @return: org.springframework.beans.PropertyValues
	 */
	@Override
	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
		//拿到注入点;这次可以直接从缓存中进行直接拿取了
		InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
		try {
			//拿到之后开始注入属性
			metadata.inject(bean, beanName, pvs);
		} catch (BeanCreationException ex) {
			throw ex;
		} catch (Throwable ex) {
			throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
		}
		return pvs;
	}

3 metadata.inject(bean, beanName, pvs);方法执行具体的注入属性的流程

注意这里的这个方法是在InjectionMetadata对象中的

	/**
	 * 真正注入属性注入的代码--lk
	 *
	 * @param target
	 * @param beanName
	 * @param pvs
	 * @MethodName: inject
	 * @return: void
	 */
	public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
		//获取单个注入点;
		Collection<InjectedElement> checkedElements = this.checkedElements;
		Collection<InjectedElement> elementsToIterate = (checkedElements != null ? checkedElements : this.injectedElements);
		//注入点不是空
		if (!elementsToIterate.isEmpty()) {
			//遍历集合获取每一个注入点(可能是属性注入点AutowiredFieldElement,也可能是方法注入点AutowiredMethodElement)
			for (InjectedElement element : elementsToIterate) {
				if (logger.isTraceEnabled()) {
					logger.trace("Processing injected element of bean '" + beanName + "': " + element);
				}
				//使用得到的住注入点开始注入(属性注入点使用AutowiredFieldElement.inject方法;方法注入点使用AutowiredMethodElement.inject方法,所以需要进入到InjectedElement的实现类中看具体的注入)
				element.inject(target, beanName, pvs);
			}
		}
	}

4 根据注入点注入我们属性,执行element.inject(target, beanName, pvs);方法

注意: 这里就牵扯到AutowiredFieldElement对象和AutowiredMethodElement对象了

下面举例会以AutowiredFieldElement为例介绍,方法类型的注入点同理,流程是一样的;

//AutowiredFieldElement对象和AutowiredMethodElement对象
/*
这两个对象其实就是我们具体的注入点,我们知道我们的@Autowired注解是可以加载属性或者方法上的,所以会有这样两个不同类型的对象
来完成不同类型注入点的处理,将Bean对象注入我们的属性中
*/
//可以看到其实AutowiredFieldElement和AutowiredMethodElement是继承了InjectedElement的
private class AutowiredFieldElement extends InjectionMetadata.InjectedElement {...}
private class AutowiredMethodElement extends InjectionMetadata.InjectedElement {...}
/**
 * 属性注入点的具体注入方法--lk
 *
 * @param bean
 * @param beanName
 * @param pvs
 * @MethodName: inject
 * @return: void
 */
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			//member是我们在寻找注入点的时候的属性,记录了注入属性的成员
			//这里获取出来后就是我们要注入的属性  private OrderService orderService;
			Field field = (Field) this.member;
			Object value;
			if (this.cached) {
				value = resolvedCachedArgument(beanName, this.cachedFieldValue);
			} else {
				//desc对象封装了属性的详情信息,包括属性的名属性类型等
				//即将注入的特定依赖项的描述符。 包装构造函数参数、方法参数或字段,允许对其元数据的统一访问。
				DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
				desc.setContainingClass(bean.getClass());
				Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
				Assert.state(beanFactory != null, "No BeanFactory available");
				TypeConverter typeConverter = beanFactory.getTypeConverter();
				try {
					//根据传递过来的beanName和类型去bean工厂找对应bean(我们可以进去看一下她到底是怎么找Bean的进入到DefaultListableBeanFactory.java)
					value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
				} catch (BeansException ex) {
					throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
				}
				synchronized (this) {
					if (!this.cached) {
						if (value != null || this.required) {
							this.cachedFieldValue = desc;
							registerDependentBeans(beanName, autowiredBeanNames);
							if (autowiredBeanNames.size() == 1) {
								String autowiredBeanName = autowiredBeanNames.iterator().next();
								if (beanFactory.containsBean(autowiredBeanName) && beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
									this.cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName, field.getType());
								}
							}
						} else {
							this.cachedFieldValue = null;
						}
						this.cached = true;
					}
				}
			}
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
				//将找到的Bean直接设置到属性中,这里的属性也就是我们加了@autowired注解的属性属性
				field.set(bean, value);
			}
		}

5 执行value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);方法根据我们提供的条件去寻找要注到属性中的Bean

注意: 这里是beanFactory进行调用的方法,

这里直接进入到DefaultListableBeanFactory即可因为就是在这个工厂中进行的寻找Bean的逻辑

//DefaultListableBeanFactory.java
/**
 * 依赖注入的时候会
 *
 * @param d
 * @param r
 * @param a
 * @param t
 * @MethodN
 * @return:
 */
@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

		descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());

		//判断是不是Optical类型
		if (Optional.class == descriptor.getDependencyType()) {
			return createOptionalDependency(descriptor, requestingBeanName);
		}
		//判断是不是对象工厂类型
		else if (ObjectFactory.class == descriptor.getDependencyType() || ObjectProvider.class == descriptor.getDependencyType()) {
			return new DependencyObjectProvider(descriptor, requestingBeanName);
		}
		//判断是不是javaxInjectProviderClass注入提供者
		else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
			return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
		}
		//此分支就是我们使用@Autowired注解之后寻找Bean会进入的分支(不是上面三种特殊类型就会进入这个分支)
		else {
			//判断属性上是不是加了@Lazy注解
			Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);
			if (result == null) {
				//没有加@Lazy注解,开始寻Bean
				result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
			}
			return result;
		}
	}

6 进入doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);执行具体的寻找Bean对象的逻辑

/**
 * 真正执行获取Bean的方法--lk
 *
 * @param descriptor
 * @param beanName
 * @param autowiredBeanNames
 * @param typeConverter
 * @MethodName: doResolveDependency
 * @return: java.lang.Object
 */
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

		InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
		try {
			Object shortcut = descriptor.resolveShortcut(this);
			if (shortcut != null) {
				return shortcut;
			}

			Class<?> type = descriptor.getDependencyType();
			//获取属性上面是否加了@Value注解
			Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
			//value!=null证明加了@Value注解
			//进行占位符填充解析$符号(这是因为@Value("${xxx}") 或者@Value("#{xxx}"))
			//${}就表示一个占位符,而#{}可以指定注入属性的名字叫什么@Value("#{orderService}")很明显#{}可以指定要注入属性的名字
			if (value != null) {
				if (value instanceof String) {
					//取出@Value()中的字符串
					String strVal = resolveEmbeddedValue((String) value);
					BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
					//解析Spring 的EL表达式,解析#符号,上面讲到#{}可以手动指定属性的名字
					//解析@Value注解中的内容,如果是${} 就进行占位符号的填充,如果是#{}会进行解析并且执行该EL表达式
					//如果是#{}中写的是要注入的Bean对象的名字,等下面方法执行结束之后就会直接返回一个Bean对象给value这也是value为什么是Object的类型原因
					value = evaluateBeanDefinitionString(strVal, bd);
				}
				//得到类型转换器: 这里用于处理当@Value注解中内容不是#{}而是一个普通的字符串,那么我们想要使用@Value注解中的内容代替我们属性的的名字
				//如: @Value("xxx") private OrderService orderService 我们想要使用xxx代替OrderService的属性名字orderService就需要定义类型转换器来完成转换
				//下面的方法就是获取我们自定义的类型转换器以便后期完成转换的
				TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
				try {
					//在有自定义类型转换器的情况下会完成类型的转换,那么我们寻找Bean对象的过程到这里直接结束返回我们的value(其实就是我们要找的Bean对象)
					return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
				} catch (UnsupportedOperationException ex) {
					// A custom TypeConverter which does not support TypeDescriptor resolution...
					//没有类型转换器,抛出异常
					return (descriptor.getField() != null ? converter.convertIfNecessary(value, type, descriptor.getField()) : converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
				}
			}

			//没有@Value注解
			//判断要注入的属性是不是 map collection 或者Array类型
			//如我们注入的属性为@Autowired private List<OrderService> orderService的话,执行完multipleBeans就不会为null
			Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
			if (multipleBeans != null) {
				//如果不为null证明要注入的属性正好是上面类型所以就直接返回即可
				return multipleBeans;
			}

			//根据Type去寻找Bean的候选者,因为根据Type寻找的Bean可能是多个所以这里使用Map封装
			//Map里面封装的可能是实例对象,也可能是Class对象
			Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
			//如果上面方法没有找到@Autowired先属性对象的Bean
			if (matchingBeans.isEmpty()) {
				//就去判断@Autowired(required=true) 也就是及required属性如果为true这里就直接抛出异常
				if (isRequired(descriptor)) {
					//此方内部就是抛出异常的方法
					raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
				}
				//如果required=false 就说明要注入的属性可以为null,这是直接返回一个null即可;
				return null;
			}

			String autowiredBeanName;
			Object instanceCandidate;

			//如果根据type查找到的Bean是多个就会进入下面分支
			if (matchingBeans.size() > 1) {
				//尝试确定出唯一一个Bean  这里返回的是Bean的名字,这个方法其实就我们所说自动注入的byName方式,所以这个方法执行完成之后一般情况下都会返回一个Bean的名字
				//前面获取获选者对象就是我们所说自动注入的根据byType
				autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
				//如果没有确定出来,执行下面分支处理特殊情况
				if (autowiredBeanName == null) {
					//@Autowired注解的required=true或者不是Array,Colection或者Map类型
					if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
						//就会抛出异常
						return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
					} else {
						// In case of an optional Collection/Map, silently ignore a non-unique case:
						// possibly it was meant to be an empty collection of multiple regular beans
						// (before 4.3 in particular when we didn't even look for collection beans).
						//否则进入到此分支,直接返回null即可
						return null;
					}
				}
				//根据返回的autowiredBeanName到候选者中寻找要注入的对象
				instanceCandidate = matchingBeans.get(autowiredBeanName);
			} else {
				// We have exactly one match.
				//如果候选者只有一个
				//这里就简单了,我们直接使用Map的迭代器将我们的Bean从候选者对象容器中取出来即可
				Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
				autowiredBeanName = entry.getKey();
				instanceCandidate = entry.getValue();
			}

			if (autowiredBeanNames != null) {
				autowiredBeanNames.add(autowiredBeanName);
			}
			//获取出来的Bean是一个Class对象的话会走这个分支解析
			if (instanceCandidate instanceof Class) {
				instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
			}
			Object result = instanceCandidate;
			//如果获取出来的实例Bean是一个NullBean(也就是空?Bean)直接返回null即可
			if (result instanceof NullBean) {
				if (isRequired(descriptor)) {
					raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
				}
				result = null;
			}
			if (!ClassUtils.isAssignableValue(type, result)) {
				throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
			}
			return result;
		} finally {
			ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
		}
	}

注意:

其实上面那个方法执行完,我们寻找Bean的过程也就执行结束了,那么好奇的地方来了,说好的先byType后ByName呢?

如果看注解你会发现有这样几个方法:

//Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
/*
判断要注入导入属性是不是Map Array Collection集合 --lk
这个方其实不难理解,当我我们注入的属性是Arry Collection 或者Map集合的时候这个方法的返回值就不会是null
例我们注入属性
@Autowired
private List<OrderService> orderServices;
这样执行此方法后直接返回一个集合即可,下面byType 或者byName就不再执行,不过话说回来,这个方法里面其实也执行了byType的寻找Bean的逻辑
就是我们下面要将的另一个方法 findAutowireCandidates(beanName, type, descriptor);
*/

	private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {

		Class<?> type = descriptor.getDependencyType();

		//是一个Stream类型
		if (descriptor instanceof StreamDependencyDescriptor) {
			//调用查找自动装配对象的候选者方法,根据type去查找自动装配的候选者Bean对象,
			//最后会以Map的形式返回回来,Key是Bean的名字,Value就是根据类型找到的Bean对象
			Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
			if (autowiredBeanNames != null) {
				autowiredBeanNames.addAll(matchingBeans.keySet());
			}
			Stream<Object> stream = matchingBeans.keySet().stream().map(name -> descriptor.resolveCandidate(name, type, this)).filter(bean -> !(bean instanceof NullBean));
			if (((StreamDependencyDescriptor) descriptor).isOrdered()) {
				stream = stream.sorted(adaptOrderComparator(matchingBeans));
			}
			return stream;
			//注入的属性是一个数组
		} else if (type.isArray()) {
			Class<?> componentType = type.getComponentType();
			ResolvableType resolvableType = descriptor.getResolvableType();
			Class<?> resolvedArrayType = resolvableType.resolve(type);
			if (resolvedArrayType != type) {
				componentType = resolvableType.getComponentType().resolve();
			}
			if (componentType == null) {
				return null;
			}
			//调用候选者方法获取
			Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, new MultiElementDescriptor(descriptor));
			if (matchingBeans.isEmpty()) {
				return null;
			}
			if (autowiredBeanNames != null) {
				autowiredBeanNames.addAll(matchingBeans.keySet());
			}
			TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
			Object result = converter.convertIfNecessary(matchingBeans.values(), resolvedArrayType);
			if (result instanceof Object[]) {
				Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
				if (comparator != null) {
					Arrays.sort((Object[]) result, comparator);
				}
			}
			return result;
			//要注入的属性是一个Collection集合
		} else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
			Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
			if (elementType == null) {
				return null;
			}
			//调用候选者方法获取
			Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType, new MultiElementDescriptor(descriptor));
			if (matchingBeans.isEmpty()) {
				return null;
			}
			if (autowiredBeanNames != null) {
				autowiredBeanNames.addAll(matchingBeans.keySet());
			}
			TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
			Object result = converter.convertIfNecessary(matchingBeans.values(), type);
			if (result instanceof List) {
				if (((List<?>) result).size() > 1) {
					Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
					if (comparator != null) {
						((List<?>) result).sort(comparator);
					}
				}
			}
			return result;
			//注入的属性是一个Map
		} else if (Map.class == type) {
			ResolvableType mapType = descriptor.getResolvableType().asMap();
			Class<?> keyType = mapType.resolveGeneric(0);
			if (String.class != keyType) {
				return null;
			}
			Class<?> valueType = mapType.resolveGeneric(1);
			if (valueType == null) {
				return null;
			}
			//调用候选者方法获取
			Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, new MultiElementDescriptor(descriptor));
			if (matchingBeans.isEmpty()) {
				return null;
			}
			if (autowiredBeanNames != null) {
				autowiredBeanNames.addAll(matchingBeans.keySet());
			}
			return matchingBeans;
			//什么都不是返回Null
		} else {
			return null;
		}
	}

//Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
/*
根据Type去寻找Bean的候选者,因为根据Type寻找的Bean可能是多个所以这里使用Map封装
Map里面封装的可能是实例对象,也可能是Class对象
说白了这个方法就是根据type去寻找Bean的,方法名也很见名知意,'查找自动装配的候选者对象'也就是说候选者里面不一定就只有一个,意思也就是根据Type寻找呗,可以看到参数中也是传递的type

具体逻辑不重要,里面执行过程无非是反射里面那一套比对逻辑(先研究一下源码可以看看)
*/
	protected Map<String, Object> findAutowireCandidates(@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {

		String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());
		Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
		for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
			Class<?> autowiringType = classObjectEntry.getKey();
			if (autowiringType.isAssignableFrom(requiredType)) {
				Object autowiringValue = classObjectEntry.getValue();
				autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
				if (requiredType.isInstance(autowiringValue)) {
					result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
					break;
				}
			}
		}
		for (String candidate : candidateNames) {
			if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
				addCandidateEntry(result, candidate, descriptor, requiredType);
			}
		}
		if (result.isEmpty()) {
			boolean multiple = indicatesMultipleBeans(requiredType);
			// Consider fallback matches if the first pass failed to find anything...
			DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
			for (String candidate : candidateNames) {
				if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) && (!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
					addCandidateEntry(result, candidate, descriptor, requiredType);
				}
			}
			if (result.isEmpty() && !multiple) {
				// Consider self references as a final pass...
				// but in the case of a dependency collection, not the very same bean itself.
				for (String candidate : candidateNames) {
					if (isSelfReference(beanName, candidate) && (!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) && isAutowireCandidate(candidate, fallbackDescriptor)) {
						addCandidateEntry(result, candidate, descriptor, requiredType);
					}
				}
			}
		}
		return result;
	}

//String autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
/*
//尝试确定出唯一一个Bean  这里返回的是Bean的名字,这个方法其实就我们所说自动注入的byName方式,所以这个方法执行完成之后一般情况下都会返回一个Bean的名字;
//前面获取获选者对象就是我们所说自动注入的根据byType
*/
	protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
		Class<?> requiredType = descriptor.getDependencyType();
		//获取出创建的对象上是否加了@Primary注解:此注解是判断该Bean对象是不是主Bean的依据
		String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
		if (primaryCandidate != null) {
			//如果是主Bean就将此Bean的名字返回
			return primaryCandidate;
		}
		//判断类上是否加了@Priority(1):注意此注解只能加载类上面,并且括号中的数字越小优先级越高,与上面的@Primary注解不一样,上面的注解是可以加在方法或者属性上的
		String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
		if (priorityCandidate != null) {
			return priorityCandidate;
		}
		// Fallback
		//上面分支都没有走就会走到循环中,这里的循环是在遍历候选者,根据name去确定一个候选者,并把该候选者的BeanName返回,
		//也就是我们说的自动注入的ByName方式
		for (Map.Entry<String, Object> entry : candidates.entrySet()) {
			String candidateName = entry.getKey();
			Object beanInstance = entry.getValue();
			if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) || matchesBeanName(candidateName, descriptor.getDependencyName())) {
				return candidateName;
			}
		}
		return null;
	}


/*
可以看到:其实我们在真正byName寻找之前还进行了前面讲的注解的判断
*/

1.4 自动注入流程总结

下面将上面的依赖注入流程做一个详细的总结,也就是将源码中注释内容连贯成一个具体流程;

下面是扫描注入点的流程:

首先容器启动会进入到一个专门用于处理依赖注入的后置处理器::AutowiredAnnotationBeanPostProcessor.java

接着进入到后置处理器的 postProcessMergedBeanDefinition 方法去寻找注入点,然而真正寻找注入点的方法是在此方法中调用的findAutowiringMetadata 方法,在这个方法中Spring会先查找缓存(injectionMetadataCache 属性),看之前是不是已经获取并存储了注入点,如果存储了直接取出来即可,

如果没有就调用buildAutowiringMetadata 方法去寻找所有加了**@Autowired @Value 或者@Inject注解** 的属性或者方法,将扫描到的属性或者方法注入点使用AutowiredFieldElement.java或者AutowiredMethodElement.java对象来封装这样两种不同类型的注入点,

然后将其add到List<InjectionMetadata.InjectedElement> elements 这样一个集合中,最后会使用new InjectionMetadata(clazz, elements)将所有的注入点封装到InjectionMetadata.java对象中,这样扫描一个类中所有的注入点的流程就结束了。

需要注意以下几点:

1 Spring会对扫描到的注入点进行缓存;

2 在扫描到的属性或者方法如果是static修饰的是不会按照注入点继续处理的,而是直接return,处理下一个满足条件的属性或者方法,在代码中体现;

3 我们最终得到的一个类中的所有满足条件的注入点会存放在InjectionMetadata.java对象中,而里面每一个注入点使用的是InjectedElement.java进行存储的,而且每一个注入点可能是方法注入的,也可能是属性注入的,所以又使用了AutowiredFieldElement.javaAutowiredMethodElement.java进行封装;

下面是属性注入的流程:

属性注入的方法也是在处理依赖注入的后置处理器::AutowiredAnnotationBeanPostProcessor.java 中;

postProcessProperties 方法中Spring会先调用我们获取注入点中的findAutowiringMetadata 方法,从缓存中拿出我们所有的注入点,也就是InjectionMetadata.java对象,这里面缓存了我们所有的注入点;

接着会调用InjectionMetadata.java对象中的inject 方法获取出我们的每一个InjectedElement.java注入点,

然后InjectedElement.java对象就会调用根据不同类型的注入点调用不同子类(AutowiredFieldElement.javaAutowiredMethodElement.java)的inject 方法具体完成属性的注入;

然后在inject 方法中会通过DefaultListableBeanFactory.javabean工厂的resolveDependency 方法去寻找我们要注入到属性中的Bean(注意执行此方法前各个参数已经从注入点中获取出来了),在此方法中会去调用doResolveDependency 方法来完成查找Bean对象的流程(注意执行doResolveDependency方法之前会先查看属性或者方法上有没有加@Lazy注解如果加了就不会去执行该方法,而是在具体使用到的时候才去执行该方法完成注入)

doResolveDependency 方法中又会去判断属性或者方法上加没有加@Value注解,执行我们上面讲的@Value注解的一部分内容;

上面流程执行完毕会调用resolveMultipleBeans 方法判断属性是不是Collection Array 或者 Map 类型的,如果是,此方法执行结束就已经封装完毕,直接返回即可,属性注入流程结束

如果不是执行则执行findAutowireCandidates 方法 去根据类型(byType)寻找我们的要注入的Bean对象;

上面方法执行完毕之后,如果返回的集合中(上面方法会返回一个Map集合)的键值对大于1证明根据type拿到了不知一个我们Bean对象,

接着就会去执行determineAutowireCandidate 方法去尝试确定出一个Bean对象,最后返回Bean的String类型的名字;

接着根据该Bean的名字从findAutowireCandidates 方法的返回值(Map)中去取出唯一一个满足我们条件的对象,到了这里寻找Bean的流程也就结束了;

最后就会回我们的的inject 方法将我们的Bean对象设置到要注入的属性中,属性注入流程结束;

同样属性注入的流程也需要注意一下几点:

1 具体执行注入流程的方法其实是我们的AutowiredFieldElement.javaAutowiredMethodElement.java对象的inject方法;

2 在根据类型寻找Bean对结束的时候如果返回的集合中没有元素,会去判断@Autowired主机的required属性是不是为true如果为true会抛出异常,为false则值返回一个null;

3 在根据Bean的名字去寻找Bean对象determineAutowireCandidate方法中。首先去判断了要注入的属性是不是加了**@Primary 以及@Priority**注解,最后才会去根据Bean的名字去寻找,最后返回找到的Bean的名字,最后到我们byType寻找到的结果集中根据这个名字获取到我们要注入到属性中的Bean。

2 总结

经过这两天的学习收获还是挺多的,为了学习一个好用的框架源码,我们前提准备工作可能都要花上数周甚至更长时间(注解,反射,设计模式…)

看到框架的源码你学习的不仅仅是一个依赖注入的流程,你更能学到Spring源码贡献者这些大牛他们脑子里的思路,他们的编程思维,

甚至他们对一个变量或者集合的判断都特别讲究。

还有就是的设计模式,这一点真的是大部分开发者的短板,这也让我看到了自己的短版和学习方向。

意识到编程真的不是仅仅是会使用API就够了,面向百度编程的思维模式在编程这条路上永远走不长;

还有就是学习的路很长,你可能努力了收获甚微,这急不得,这是一个积累的过程,沉淀的过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

liu.kai

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

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

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

打赏作者

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

抵扣说明:

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

余额充值