Java-spring对象生命周期

一、生命周期流程

图1.1 bean生命周期

 二、怎样解决循环依赖问题

循环依赖情况:A 依赖 B,B依赖A 

图2.1 循环依赖生命周期流程

2.1 一级缓存:单例池 singletonObjects

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

单例 Bean 在创建完成后放入 singletonObjects 这个 Map 里面,这就是一级缓存。图三中 getBean("B") 的过程中,a 是没有加入到一级缓存中,所以在 getBean("B") 的流程中,b 依赖了 a,此时 b 是找不到 a 对象的。依然会无法解决循环引用的问题。

2.2 二级缓存:earlySingletonObjects

private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

earlySingletonObjects 这个 map 我们打算存放提前暴露 bean 的引用,发生在实例化以后,我们就把对象放入到 earlySingletonObjects 这个 map 中,这样在 加载 b 的过程中,b.setA(getBean("a")),我们就可以在 earlySingletonObjects 拿到 a 的引用,此时 a 仅仅经过了实例化,并没有设置属性。流程如下:

图2.2 加二级缓存示意图

问题:在初始化后,会调用AOP,会生成全新的对象增强后的 a、b 对象,此时 a 对象中依赖的 b 为增强后的,而 b 对象依赖的 a 是为原始对象,未增强的。所以使用二级缓存解决不了循环依赖中发生过 aop 的引用问题。具体位置如下图:

图2.3 加二级缓存和AOP示意图

2.3 三级缓存:singletonFactories

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

为了解决二级缓存中 AOP 生成新对象的问题,Spring 中的解决方案是:提前 AOP:在 加载 B 的流程中,如果发生了循环依赖,就是说 b 又依赖了 a,我们就要对 a 执行 aop,提前获取增强以后的 a 对象,这样 b 对象依赖的 a 对象就是增强以后的 a 了。三级缓存的 key 是 beanName,value 是一个 lambda 表达式,这个 lambda 表达式的作用就是进行提前 AOP。

图2.4  加三级缓存和AOP示意图

上面就是三级缓存的作用,其中有个三级缓存到二级缓存的升级过程,这个非常重重要,这个主要是防止重复 aop。

 三、构造器注入

构造器注入,即spring初始化的流程中,构造方法的选择情况。

总的来说,Spring的构造方法,总体分两类,第一类是普普通通的构造函数,第二类则是有@Autowired注解的构造函数。下面的列表展示了Spring启动的时候,对于要初始化的bean,其可能的构造函数的具体情况。

在构造函数没有被@Autowired注解的情况下,具体情况大致分为:

  1. 只有一个无参构造函数。
  2. 只有一个含参构造函数。
  3. 有若干个构造函数,但是包含一个无参构造函数。
  4. 有若干个构造函数,但是不包括一个无参构造函数。

总体上来说,对于上面的四种情况处理的非常简单。当Spring只有一个构造函数的时候,将会直接使用这个构造函数来进行初始化。无论这个构造函数究竟是含参的还是不含参的,但是对于含参的构造函数来说,构造器所依赖的必须要被满足。

在有多个构造函数的情况下,也非常简单,如果构造函数中有无参构造函数,那么使用无参构造函数进行初始化。如果没有构造函数,那么Spring将会直接抛出异常,提示用户该bean无法实例化,需要用户指明Spring要选用哪一个构造方法,或者选用哪几个构造方法。指明选用的构造器的方法也很简单,在构造器上加上@Autowired表明你希望Spring使用这个构造器来进行初始化构造。

在构造函数被@Autowired注解的情况下,@Autowired可以通过true、false来设置强依赖或者弱依赖,具体情况大致分为:

  1. 只有一个@Autowired修饰的构造函数。
  2. 有多个一个@Autowired或修饰的构造函数。

对于第一种情况,非常的简单,如果使用@Autowired注解了这个构造器,那么将会使用这个构造器进行构造。在@Autowired的required属性为true的使用,强制使用这个构造函数。如果required属性为false的话,那么在这个构造函数所需要的参数不被满足的情况下,如果有默认的无参构造函数,Spring还会调用默认的无参构造函数来进行初始化。如果没有默认的无参构造函数,那么非常抱歉,将会抛出异常。

如果有多个@Autowired注解的构造函数,如果这里面存在required=true的构造函数,那么将会直接抛出异常。如果有@Autowired(required=true)注解构造函数,那么这个bean有且只能有这一个被@Autowired注解的构造函数。如果有多个,无论其它的是不是@Autowired(required=false),都会被认作是多余的,将直接抛出异常。

如果Spring发现了一个bean有多个@Autowired(required=false)修饰的构造函数。那么Spring将会在创建一个构造器的候选列表,通过在这个列表中进行筛选,选择Spring认为最合适的构造方法。如果该类中还有一个默认的构造函数,那么还会将这个默认的构造函数放入到构造器的候选列表当中去,以防在所有的@Autowired都没有被满足的情况下,仍旧能够使用默认的无参构造函数进行构造。

Spring对Kotlin进行了特别的处理,在Kotlin中还有首选构造函数。如果Spring发现了Kotlin中的首选构造函数,还会根据首选构造函数进行一些处理,大致和无参构造函数类似。

在有多个@Autowired(required=false)的时候,Spring采用差异度算法,会对这些构造器进行特别的处理。

Spring针对多个@Autowired(required=false)进行选择时所采取的算法:
Spring在面对多个构造函数的时候,首先会对这些构造函数根据参数的个数和权限的大小(“public default protected private”)进行排序。然后按照先后顺序进行处理。(先差异度,后顺序)

Spring对多个构造函数,采用差异度的大小来进行排序,然后选择差异度最小的那个构造函数进行初始化。如果有多个差异度相同的构造函数,那么按照处理的顺序,采用最小差异度中第一个被处理的构造函数。

参考:

Spring循环依赖及解决方式_Java_张sir_InfoQ写作社区

Spring启动分析之多个构造器注入选择 - SegmentFault 思否

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值