Spring原理篇--Spring最经常使用的一个功能 依赖注入, 该功能源码是一定需要知道的;这是我们日常开发中的核心; 了解其源码;这一篇就够了;

Posted 喜欢编码的老胡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring原理篇--Spring最经常使用的一个功能 依赖注入, 该功能源码是一定需要知道的;这是我们日常开发中的核心; 了解其源码;这一篇就够了;相关的知识,希望对你有一定的参考价值。

@TOC# Spring系列
记录在程序走的每一步___auth:huf


回复:

在文章的前面会 回复一些同学的疑问 注意 在使用InstantiationAwareBeanPostProcessor 的时候 如果return 返回值 不写; 此时正在创建对象过程当中; 这时候 会导致 该对象里面的@Autowired 注入失败; 对象不完整; 这时候请注意 InstantiationAwareBeanPostProcessor 的写法; 因为此时你 正在介入Bean的 实例化;


Spring 依赖注入


此处作为一个Spring的一个新的篇章; Bean的生命周期已经在上面的篇幅已经陈述完了 ; 现在进入新的篇章 Spring 依赖注入;

首先来一个 二连问:

第一问: 依赖注入有几种方式?

在面试中; 该问题不会经常被问到; 但是如果问这个问题的人 可以看出 该面试官 对Spring 肯定是有一定理解的; 你怎么答这个问题才能体现自己对Spring的理解?

Spring中 分两种 依赖注入的方式:

1 手动注入

为什么会谈先手动注入; 在很久很久以前; 我们再Spring中写注入的时候; 我们是再XML文件里面写注入的;我当时在一家跟政府合作的企业中进行开发; 那里的配置文件 体量非常非常大 一个文件少说有几千行配置在里面; 里面用的方式 就是XML 注入方式;手动注入; 当时也遇到了人生中的第一位导师~而且是女导师 偏了… 继续~
1:通过set的方式注入;

<bean name="studentService" class="com.huf.service.impl.StudentServiceImpl">
	<property name="studentMapper" ref="studentMapper"/>
</bean>

2:通过构造方法方式注入;

<bean name="studentService" class="com.huf.service.impl.StudentServiceImpl">
	<constructor‐arg index="0" ref="studentMapper"/>
</bean>

所以手动注入的方式 再分两种 : 一: 通过set方式注入; 二:通过构造方式注入;

2 自动注入

1: XML的 autowire 自动注入;
在XML的bean标签中 有一个属性; 是autowire 里面有几个参数 :
1). byType 2). byName 3). constructor 4). default 5). no

<bean name="studentService" class="com.huf.service.impl.StudentServiceImpl" autowire = "byType">
</bean>

这么写,表示Spring会自动的给userService中所有的属性自动赋值(不需要这个属性上有 @Autowired注解,但需要这个属性有对应的set方法)
以上这些方式 感兴趣的朋友可以一个一个去尝试; 该篇文章 比较长; 这里就不做过多的演示;

源码: 在创建Bean的过程当中 在填充属性的时候.Spring会去解析当前这个类的所有方法;在解析的过程当中 Spring会得到 PropertyDescriptor 叫做属性描述器的类 这个类的作用 就是描述 某个属性的 get 与 set 方法; PropertyDescriptor 是java.bean 的一个类; 有喜欢的小伙伴 可以自己去查看以下; 这个类挺方便的; 以下就是我自己个人 使用这个类的一个简单实例:

通过属性名字 以及 类class 创建PropertyDescriptor  
PropertyDescriptor pd = new PropertyDescriptor(declaredField.getName(), clazz);
通过这个类可以得到Method方法; 这是读方法 也就是get
Method method = pd.getReadMethod();
Object invoke = method.invoke(obj);
String v = execute((String) invoke, maskStr, staNumber, endNumber);
这是写方法 也就是set;
pd.getWriteMethod().invoke(obj, v);

重点

2:@Autowired注解的自动注入; 我们重点关注的Autowired

Essentially, the @Autowired annotation provides the same 
capabilities as described in Autowiring Collaborators but with more 
fine‐grained control and wider applicability

先看这一段 本质上 @Autowired 提供了相同的功能; 但是拥有更细粒度的控 制和更广泛的适用性;

@Autowired 作用更细粒度;

XML中的autowire控制的是整个bean的所有属性,而@Autowired注解是直接写在某个属性、某个 set方法、某个构造方法上的; @Autowired 是Xml当中 ByType 与 ByName的结合

	 先根据属性类型去找Bean,如果找到多个再根据属性名确定一个
	 @Autowired
     private StudentMapper studentMapper;
     先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个
     @Autowired
     public void setStudentMapper (StudentMapper studentMapper){
     }
     先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个
     @Autowired
     public StudentServiceImpl(StudentMapper studentMapper) {
    }
  1. 属性注入
  2. set方法注入
  3. 构造方法注入

以上就是 我们的理论知识点;


第二问 @Autowired 是怎么注入进来的?

我们在第一问讲了 是根据类型先找 然后再根据名字选出; 那么 是怎么找的? 我们带着疑问 一起来一探究竟

以上 就是简简单单的一个注入; 在我们Spring进行生命周期的时候 会通过RootBeanDefinition 以及 非常多的BeanPostProcessor 实例化 以及 初始化这个类; 实例化就是创建这个Bean 在创建完成之后 经过初始化; 完成整个Bean的 构建 最后保存在单例池里 这里 就是Bean创建完成之后的代码 也就是 属性注入;

以下源码中穿插自己对源码的见解; 源码是Spring 5.3.5 版本的;
现在我们以Debug的方式 进行依赖注入代码追踪;
前面的省… 在 AbstractBeanFactory 在这个类中: doGetBean方法中 我们可以体会到整个Bean的创建流程; 上一章节我们在 该源码中说道 有兴趣的 调到上面一个章节 直接搜索 doCreateBean 里面写到;

	protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
		开始实例化Bean
		BeanWrapper instanceWrapper = null;
		判断Bean是不是单例的 如果是
		if (mbd.isSingleton()) {
			先从Factory 删除Bean; 这里 就是个ConcurrentMap 也就是删除正在创建的Bean的包装对象;
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		删除了之后 是不是null;  然后开始createBeanInstance
		if (instanceWrapper == null) {
			开始进入createBeanInstance
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		以下源码暂时不解析;  在之后的篇章会详细说的; 目前就到这里可以了
-----------------------------------------------------------------------------
		这里接上之前的源码  把该方法源码全部展示完:
		
		获取 实例化完成的Bean  这时候 Bean里面的属性是空的
		Object bean = instanceWrapper.getWrappedInstance();
		获取了 正在创建的Bean class文件; 
		Class<?> beanType = instanceWrapper.getWrappedClass();
		这里不用多说也应该可以看明白;
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}
		这里就是执行我们后置MergedBeanDefinitionPostProcessors 后置处理器的调度方法; 
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}

		缓存单例,以便能够解析循环引用
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			该方法 是解决循环依赖; 循环依赖将放在下个篇章去讲;
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		这时候 开始了Bean的属性注入;  
		Object exposedObject = bean;
		try {
			这个方法 是主要方法; 
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		以下源码全部省去;  进入populateBean里面去看看;
	}

进入 populateBean

	@SuppressWarnings("deprecation")
	protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
		按照惯例 先隐藏前面代码; 方便阅读;
		前面有说道 会先去拿它的PropertyDescriptor 这个东西是什么作用的 自己去看上面的我写的案例;
		PropertyDescriptor[] filteredPds = null;
		if (hasInstAwareBpps) {
			if (pvs == null) {
				pvs = mbd.getPropertyValues();
			}
			循环当前BeanBeanPostProcessor BP 
			 在5.3.5 版本有6个 其他版本可能个数会不一样
			 主要的一个BP 是 : AutowiredAnnotationBeanPostProcessor
			for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
				PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
				if (pvsToUse == null) {
					if (filteredPds == null) {
						filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
					}
					pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
					if (pvsToUse == null) {
						return;
					}
				}
				pvs = pvsToUse;
			}
		}
		隐藏后面代码

这个for循环这个List给你们打印出来

在通过了这个bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
会往BeanWrapper.getWrappedInstance() 这里 保存的值 的内存引用 就是单例池里面的相对应需要注入的哪个Bean. 所以 这里将他注入; 那么外面哪个Bean里面就有值了;

AutowiredAnnotationBeanPostProcessor

postProcessProperties

	@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;
	}

inject

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
		这个源码熟吧? 里面的InjectedElement 放着就是 你依赖注入的每一个属性;
		Collection<InjectedElement> checkedElements = this.checkedElements;
		Collection<InjectedElement> elementsToIterate =
				(checkedElements != null ? checkedElements : this.injectedElements);
		if (!elementsToIterate.isEmpty()) { 
			for (InjectedElement element : elementsToIterate) {
				element.inject(target, beanName, pvs);
			}
		}
	}

inject2 进入这个inject的时候要注意; 是这个;

@Override
		protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			filed就是我们要注入的属性;
			Field field = (Field) this.member;
			
			Object value;
			检查缓存
			if (this.cached) {
				try {
					value = resolvedCachedArgument(beanName, this.cachedFieldValue);
				}
				catch (NoSuchBeanDefinitionException ex) {
					value = resolveFieldValue(field, bean, beanName);
				}
			}
			else {
				如果没有缓存的情况下 走resolveFieldValue
				value = resolveFieldValue(field, bean, beanName);
			}
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
				field.set(bean, value);
			}
		}

resolveFieldValue

最后在上面那个方法中的最后 注入这个属性;

@Nullable
		private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
			传入之后 先看你的required 是否为false;Autowired 里面的那个required 表示是否一定要注入;默认为true
			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();
			Object value;
			try {
				通过这个步骤找到了 value 
				value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
			}
			------省掉部分代码
			返回这个value;
			return value;
		}
	}

总结

寻找注入点:

在创建一个Bean的过程中,Spring会利用AutowiredAnnotationBeanPostProcessor的 **postProcessMergedBeanDefinition()**找出注入点并缓存,找注入点的流程为:

  1. 遍历当前类的所有的属性字段Field
  2. 查看字段上是否存在@Autowired、@Value、@Inject中的其中任意一个,存在则认为该字段 是一个注入点
  3. 如果字段是static的,则不进行注入
  4. 获取@Autowired中的required属性的值
  5. 将字段信息构造成一个AutowiredFieldElement对象,作为一个注入点对象添加到 currElements集合中。
  6. 遍历当前类的所有方法Method
  7. 判断当前Method是否是桥接方法,如果是找到原方法
  8. 查看方法上是否存在@Autowired、@Value、@Inject中的其中任意一个,存在则认为该方法 是一个注入点
  9. 如果方法是static的,则不进行注入
  10. 获取@Autowired中的required属性的值
  11. 将方法信息构造成一个AutowiredMethodElement对象,作为一个注入点对象添加到 currElements集合中。
  12. 遍历完当前类的字段和方法后,将遍历父类的,直到没有父类
  13. 最后将currElements集合封装成一个InjectionMetadata对象,作为当前Bean对于的注入点集合 对象,并缓存。

static的字段或方法为什么不支持

我们假设:
在属性中 我们的Bean是原型Bean 也就是多例的 那么 他属性如果用了static会怎么样? 这个问题如果想明白了 就知道为什么不支持了.

在static修饰的方法中 其字节码文件会生成两个同样的方法 其中一个方法带有 synthetic bridge 并且都是存在@Autowired注解的 所以在Spring中需要处理这种情况,当遍历到桥接方法时,得找到原方法

注入点进行注入

Spring在AutowiredAnnotationBeanPostProcessor的**postProcessProperties()**方法中,会遍 历所找到的注入点依次进行注入。

字段注入

  1. 遍历所有的AutowiredFieldElement对象
  2. 将对应的字段封装为DependencyDescriptor对象
  3. 调用BeanFactory的resolveDependency()方法,传入DependencyDescriptor对象,进行依 赖查找,找到当前字段所匹配的Bean对象。
  4. 将DependencyDescriptor对象和所找到的结果对象beanName封装成一个 ShortcutDependencyDescriptor对象作为缓存,比如如果当前Bean是原型Bean,那么下次 再来创建该Bean时,就可以直接拿缓存的结果对象beanName去BeanFactory中去那bean对象 了,不用再次进行查找了
  5. 利用反射将结果对象赋值给字段。

Set方法注入

  1. 遍历所有的AutowiredMethodElement对象
  2. 遍历将对应的方法的参数,将每个参数封装成MethodParameter对象
  3. 将MethodParameter对象封装为DependencyDescriptor对象
  4. 调用BeanFactory的resolveDependency()方法,传入DependencyDescriptor对象,进行依 赖查找,找到当前方法参数所匹配的Bean对象。
  5. 将DependencyDescriptor对象和所找到的结果对象beanName封装成一个 ShortcutDependencyDescriptor对象作为缓存,比如如果当前Bean是原型Bean,那么下次 再来创建该Bean时,就可以直接拿缓存的结果对象beanName去BeanFactory中去那bean对象 了,不用再次进行查找了
  6. 利用反射将找到的所有结果对象传给当前方法,并执行。

本章节的重点已经全部陈述完成; 如果想听其他注解的源码 可以留言; 如果有时间的话 我会逐一给给同学们解释清楚;

SEE YOU

以上是关于Spring原理篇--Spring最经常使用的一个功能 依赖注入, 该功能源码是一定需要知道的;这是我们日常开发中的核心; 了解其源码;这一篇就够了;的主要内容,如果未能解决你的问题,请参考以下文章

Spring原理篇(17)--Spring事务的传播机制;该篇章是Spring原理篇的最后一章;

最基础的SSM框架整合篇

Spring Boot 2从入门到入坟 | Web场景开发篇:超硬核两万四千多字,全网最详细源码分析之静态资源配置原理,不是你来砍我

#yyds干货盘点# 爆肝30天,肝出来史上最透彻Spring原理和27道高频面试题总结

Spring MVC:原理与使用

Spring原理篇(14)--Spring AOP源码的实现<一>