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) {
}
- 属性注入
- set方法注入
- 构造方法注入
以上就是 我们的理论知识点;
第二问 @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();
}
循环当前Bean的 BeanPostProcessor 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()**找出注入点并缓存,找注入点的流程为:
- 遍历当前类的所有的属性字段Field
- 查看字段上是否存在@Autowired、@Value、@Inject中的其中任意一个,存在则认为该字段 是一个注入点
- 如果字段是static的,则不进行注入
- 获取@Autowired中的required属性的值
- 将字段信息构造成一个AutowiredFieldElement对象,作为一个注入点对象添加到 currElements集合中。
- 遍历当前类的所有方法Method
- 判断当前Method是否是桥接方法,如果是找到原方法
- 查看方法上是否存在@Autowired、@Value、@Inject中的其中任意一个,存在则认为该方法 是一个注入点
- 如果方法是static的,则不进行注入
- 获取@Autowired中的required属性的值
- 将方法信息构造成一个AutowiredMethodElement对象,作为一个注入点对象添加到 currElements集合中。
- 遍历完当前类的字段和方法后,将遍历父类的,直到没有父类
- 最后将currElements集合封装成一个InjectionMetadata对象,作为当前Bean对于的注入点集合 对象,并缓存。
static的字段或方法为什么不支持
我们假设:
在属性中 我们的Bean是原型Bean 也就是多例的 那么 他属性如果用了static会怎么样? 这个问题如果想明白了 就知道为什么不支持了.
在static修饰的方法中 其字节码文件会生成两个同样的方法 其中一个方法带有 synthetic bridge 并且都是存在@Autowired注解的 所以在Spring中需要处理这种情况,当遍历到桥接方法时,得找到原方法
注入点进行注入
Spring在AutowiredAnnotationBeanPostProcessor的**postProcessProperties()**方法中,会遍 历所找到的注入点依次进行注入。
字段注入
- 遍历所有的AutowiredFieldElement对象
- 将对应的字段封装为DependencyDescriptor对象
- 调用BeanFactory的resolveDependency()方法,传入DependencyDescriptor对象,进行依 赖查找,找到当前字段所匹配的Bean对象。
- 将DependencyDescriptor对象和所找到的结果对象beanName封装成一个 ShortcutDependencyDescriptor对象作为缓存,比如如果当前Bean是原型Bean,那么下次 再来创建该Bean时,就可以直接拿缓存的结果对象beanName去BeanFactory中去那bean对象 了,不用再次进行查找了
- 利用反射将结果对象赋值给字段。
Set方法注入
- 遍历所有的AutowiredMethodElement对象
- 遍历将对应的方法的参数,将每个参数封装成MethodParameter对象
- 将MethodParameter对象封装为DependencyDescriptor对象
- 调用BeanFactory的resolveDependency()方法,传入DependencyDescriptor对象,进行依 赖查找,找到当前方法参数所匹配的Bean对象。
- 将DependencyDescriptor对象和所找到的结果对象beanName封装成一个 ShortcutDependencyDescriptor对象作为缓存,比如如果当前Bean是原型Bean,那么下次 再来创建该Bean时,就可以直接拿缓存的结果对象beanName去BeanFactory中去那bean对象 了,不用再次进行查找了
- 利用反射将找到的所有结果对象传给当前方法,并执行。
本章节的重点已经全部陈述完成; 如果想听其他注解的源码 可以留言; 如果有时间的话 我会逐一给给同学们解释清楚;
SEE YOU
以上是关于Spring原理篇--Spring最经常使用的一个功能 依赖注入, 该功能源码是一定需要知道的;这是我们日常开发中的核心; 了解其源码;这一篇就够了;的主要内容,如果未能解决你的问题,请参考以下文章
Spring原理篇(17)--Spring事务的传播机制;该篇章是Spring原理篇的最后一章;
Spring Boot 2从入门到入坟 | Web场景开发篇:超硬核两万四千多字,全网最详细源码分析之静态资源配置原理,不是你来砍我