面试SpringBoot收藏

【Spring源码】9.IOC之创建bean对象之构造方法

2022-01-24  本文已影响0人  天还下着毛毛雨
image

前言

Bean的创建

前面降到,Bean的创建方式又有以下几种:

  1. factoryMethod :
    1. FactoryBean + FactoryBean的非静态FactoryMethod
    2. 当前beanClass + 静态FactoryMethod
  2. 构造方法
    1. 带有@Autowired注解的有参构造
    2. 不带有@Autowired注解的有参构造
    3. 无参构造

上一篇讲了 FactoryMethod 方式 是如何来创建bean的,

这一篇 则主要介绍 Spring源码中 是如何通过 构造方法 来创建bean的实例的。

源码还是回到创建bean这里

image

如果不使用FactoryMethod,就走构造器 创建bean

image

1. 缓存

在判断是否使用FactoryMethod方式的代码下方,紧接着的就是 判断 该类型的bean 是否实例化过, 之前实例化 是否缓存了 构造方法, 和是否使用了构造方法的参数注入。

相关数据的缓存是存在beanDefinition里的 :

  1. resolvedConstructorOrFactoryMethod : 使用过的构造方法 或者 FactoryMethod。
  2. constructorArgumentsResolved : 构造方法 是否 使用了参数注入
  3. resolvedConstructorArguments : 一般如果constructorArgumentsResolved = true, 使用了参数注入,beanDefinition的resolvedConstructorArguments 将会缓存 参数列表 获取到 的 具体值。
image

存在缓存

如果这个bean 被处理过

  1. 使用了参数注入, 用有参的构造方法 来 创建bean。会从beanDefinition里取出 缓存的构造方法和 参数列表 直接反射这个方法,创建对象。
  2. 没有使用参数注入, 走无参构造方法。 反射无参构造方法。

记住这两个方法 :

  1. autowireConstructor(beanName, mbd, null, null) : 用有参的 构造方法 创建对象。
  2. instantiateBean(beanName, mbd) :用无参构造方法 创建对象。
image

作用

单例bean的话, 是用不到这个缓存的,因为对象实际只创建一次, 就是只走一次 不存在缓存,自己选择构造器 创建对象 的逻辑。第二次获取对象 从单例池里拿,直接返回, 不会再走到 创建对象的代码了。

多例Bean的话, 会使用到。每次都创建新对象, 而且多个对象 共用的是同 一个 beanDefinition ,会命中存在beanDefinition里的缓存。

2. 选择构造器

单例Bean 第一次创建对象, 肯定是 不存在缓存的,需要自己选择合适的构造器。

image

这里又是BeanPostProcessor的运用, AutowiredAnnotationBeanPostProcessor 类 的实例 会再这里, 进行构造器的 选择。

image

点进AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(Class<?> beanClass, final String beanName):

方法一开始是处理 @Lookup注解的, 略过。

image

先剩下的总体代码 :

image

2.1 寻找 @Autowired 注解 的构造器

image
  1. 遍历class的所有构造方法
  2. 判断当前遍历的构造方法是否存在@Autowired注解
    1. 存在,判断是否找到 @Autowired(required = true)
      1. 已经找到,报错
      2. 没有找到, 判断自己required属性是否为true
        1. 是为true, 判断候选的构造方法集合中是否有元素
          1. 已经找到,报错。
          2. 没找到,将设置设置为 局部变量requiredConstructor,用来标识已经找到@Autowired(required = true) 的构造方法, 加到 候选的构造方法集合中。
        2. 为false,加到 候选的构造方法集合中

总结:

其实就是 不允许 存在 同时存在 @Autowired(required = true) 和 @Autowired(required = false)的构造方法, 可以允许同时存在 多个 @Autowired(required = false) 的 构造方法。

2.2 判断寻找 @Autowired 注解 的构造器的结果

  1. 要么找到了@Autowired修饰的构造方法,返回
  2. 没找到@Autowired修饰的构造方法,看是不是 只存在 一个 有参构造方法 , 有的话就返回它
  3. 否则,就是返回null, 走无参构造。
image

3. 根据返回的构造器 进行实例化 :

3.1. 没找到合适的构造器:

如果选择构造器中,没有找到构造器, 这里返回null, 直接走无参构造

image image

最终获取无参构造, 反射出实例,返回。

image

3.2. 找到了 构造器列表

如果找到了构造器列表,走有参构造 实例化

image

3.2.1 判断是否要从缓存里取构造器和参数列表

在之前命中缓存的时候, 这个explicitArgs参数传的就是null,这个时候就从BeanDefinition的缓存里取构造器和参数列表

image

如果缓存里有,那么constructorToUse !=null, argsToUse != null, 下面很大的判断就不会进,直接 跳到 方法最后面, 利用方法和参数 直接反射。

image image

反射

image

3.2.2 不走缓存, 筛选构造器

3.2.2.1 是否只找到一个无参构造

没走缓存, 就会进这个判断

image

如果只找到一个构造器,并且是无参构造, 直接实例化

image

3.2.2.2 按照参数列表排序

image

3.2.2.3 按照顺序遍历构造方法,找到一个所有传入参数都能从容器中注入的构造方法

由于构造方法的列表已经按照参数列表的长度排过序了, 长度越长的越先遍历 , 如果当前遍历的 构造方法,的所有参数都能从beanFactory中注入进来,那么

最终用来 实例化对象的构造方法和列表就是用的 当前遍历的这个。

image
参数注入

其中参数注入,如果参数是引用类型会 触发 getBean()操作。

image

createArgumentArray()方法下半部分 :

image image image
String类型的参数会从配置文件里 解析 @Value值
image
引用类型参数,触发getBean():
image image image

最終调用的就是 beanFactory.getBean(beanName)

image

3.2.2.4 缓存构造方法和参数列表

构造方法和获取到的参数列表找到之后,实例化之前,缓存一下。

image

3.2.2.5 反射构造方法和获取到的参数列表

回到ConstructorResolver.autowireConstructor()

image

4. 总结

  1. 带有@Autowired注解的有参构造,参数getBean
    1. 存在且只有一个required = true,不存在其他带有@Autowired注解的构造方法,就执行这个
      如果参数不能都成功注入,就会报错NoSuchBeanDefinitionException
    2. 没有required = true,却存在多个required = false
      会选择参数最长,且所有参数都能成功注入的构造方法
      如果参数都不能注入,就会报错NoSuchBeanDefinitionException
  2. 不带有@Autowired注解的有参构造
    1. 存在多个,会调用无参构造
    2. 仅存在一个,就用这个,参数getBean
      如果参数不能都成功注入,就会报错NoSuchBeanDefinitionException
  3. 无参构造
    直接反射

最后,被选中的构造方法 参数 如果是 字符串会从 配置文件里取(类似@Value), 如果是 引用类型 ,就是 BeanFactory.getBean()。

上一篇下一篇

猜你喜欢

热点阅读