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

@TOC# Spring系列
记录在程序走的每一步___auth:huf
回复: 【Spring|Spring原理篇(7)--Spring最经常使用的一个功能 依赖注入, 该功能源码是一定需要知道的;这是我们日常开发中的核心; 了解其源码;这一篇就够了;】在文章的前面会 回复一些同学的疑问 注意 在使用InstantiationAwareBeanPostProcessor 的时候 如果return 返回值 不写; 此时正在创建对象过程当中; 这时候 会导致 该对象里面的@Autowired 注入失败; 对象不完整; 这时候请注意 InstantiationAwareBeanPostProcessor 的写法; 因为此时你 正在介入Bean的 实例化;

Spring 依赖注入 此处作为一个Spring的一个新的篇章; Bean的生命周期已经在上面的篇幅已经陈述完了 ; 现在进入新的篇章 Spring 依赖注入;
首先来一个 二连问: 第一问: 依赖注入有几种方式? 在面试中; 该问题不会经常被问到; 但是如果问这个问题的人 可以看出 该面试官 对Spring 肯定是有一定理解的; 你怎么答这个问题才能体现自己对Spring的理解?
Spring中 分两种 依赖注入的方式:
1 手动注入 为什么会谈先手动注入; 在很久很久以前; 我们再Spring中写注入的时候; 我们是再XML文件里面写注入的; 我当时在一家跟政府合作的企业中进行开发; 那里的配置文件 体量非常非常大 一个文件少说有几千行配置在里面; 里面用的方式 就是XML 注入方式; 手动注入; 当时也遇到了人生中的第一位导师~而且是女导师 偏了… 继续~
1:通过set的方式注入;


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

所以手动注入的方式 再分两种 : 一: 通过set方式注入; 二:通过构造方式注入;
2 自动注入 1: XML的 autowire 自动注入;
在XML的bean标签中 有一个属性; 是autowire 里面有几个参数 :
1). byType 2). byName 3). constructor 4). default 5). no

这么写,表示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|Spring原理篇(7)--Spring最经常使用的一个功能 依赖注入, 该功能源码是一定需要知道的;这是我们日常开发中的核心; 了解其源码;这一篇就够了;
文章图片

以上 就是简简单单的一个注入; 在我们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里面就有值了;
Spring|Spring原理篇(7)--Spring最经常使用的一个功能 依赖注入, 该功能源码是一定需要知道的;这是我们日常开发中的核心; 了解其源码;这一篇就够了;
文章图片

AutowiredAnnotationBeanPostProcessor postProcessProperties
@Override public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {顾名思义; 应该可以知道这个方法里面是在做些什么事情; InjectionMetadata metadata = https://www.it610.com/article/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 checkedElements = this.checkedElements; Collection elementsToIterate = (checkedElements != null ? checkedElements : this.injectedElements); if (!elementsToIterate.isEmpty()) {for (InjectedElement element : elementsToIterate) {element.inject(target, beanName, pvs); } } }

inject2 进入这个inject的时候要注意; 是这个; Spring|Spring原理篇(7)--Spring最经常使用的一个功能 依赖注入, 该功能源码是一定需要知道的;这是我们日常开发中的核心; 了解其源码;这一篇就够了;
文章图片

@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 = https://www.it610.com/article/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 autowiredBeanNames = new LinkedHashSet<>(1); Assert.state(beanFactory != null, "No BeanFactory available"); TypeConverter typeConverter = beanFactory.getTypeConverter(); Object value; try {通过这个步骤找到了 value value = https://www.it610.com/article/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

    推荐阅读