SpringBean的生命周期

一、传统 Bean 的生命周期

  1. new实例化;
  2. 可使用了
  3. 无引用时,GC回收。

二、Servlet 的生命周期

  1. 实例化Servlet对象;
  2. init初始化对象;
  3. 相应客户端请求service()(doGet()与doPost());
  4. destroy()终止/销毁。

三、Spring Bean的生命周期

  1. 实例化对象;
  2. 填充属性值及引用;
  3. 调用 BeanNameAware 的 setBeanName(String name) 设置 bean 的 id;
  4. 调用 BeanFactoryAware 的 setBeanFactory(BeanFactory beanFactory) 设置 BeanFactory Bean工厂;
  5. 同上:ApplicationContextAwaresetApplicationContext(ApplicationContext applicationContext)
  6. 如果实现 BeanPostProcessor,则 调用 postProcessBeforeInitialization() 初始化前的后置处理方法
  7. 如果实现了 InitializingBean 接口,则使用 afterPropertiesSet() 来初始化属性
  8. 如果实现 BeanPostProcessor,则 调用 postProcessAfterInitialization() 初始化后的后置处理方法
  9. 此时,bean 就可以使用了
  10. DisposableBean接口 destroy() 销毁bean。不过在Spring5.0开始,DisposableBean.destroy() 已经是过时的方法了,可直接使用 close()。

Spring 如何解决循环依赖的问题

https://zhuanlan.zhihu.com/p/84267654

https://blog.csdn.net/qq_36381855/article/details/79752689

Spring 如何解决循环依赖的问题

Zeus_龙 2018-03-31 21:35:03  59190  收藏 227
分类专栏: Spring学后知识汇总 文章标签: Spring循环依赖问题

(一)Spring  IOC容器---对象循环依赖

1. 什么是循环依赖?  what?

(1)循环依赖-->循环引用。--->即2个或以上bean 互相持有对方,最终形成闭环。

eg:A依赖B,B依赖C,C又依赖A。【注意:这里不是函数的循环调用【是个死循环,除非有终结条件】,是对象相互依赖关系】

2.  Spring中循环依赖的场景?where?

①:构造器的循环依赖。【这个Spring解决不了】

StudentA有参构造是StudentB。StudentB的有参构造是StudentC,StudentC的有参构造是StudentA ,这样就产生了一个循环依赖的情况,

我们都把这三个Bean交给Spring管理,并用有参构造实例化

  1. public class StudentA {
  2. private StudentB studentB ;
  3. public void setStudentB(StudentB studentB) {
  4. this.studentB = studentB;
  5. }
  6. public StudentA() {
  7. }
  8. public StudentA(StudentB studentB) {
  9. this.studentB = studentB;
  10. }
  11. }
[java]  view plain  copy

 
 
  1. public class StudentB {
  2. private StudentC studentC ;
  3. public void setStudentC(StudentC studentC) {
  4. this.studentC = studentC;
  5. }
  6. public StudentB() {
  7. }
  8. public StudentB(StudentC studentC) {
  9. this.studentC = studentC;
  10. }
  11. }
[java]  view plain  copy

 
 
  1. public class StudentC {
  2. private StudentA studentA ;
  3. public void setStudentA(StudentA studentA) {
  4. this.studentA = studentA;
  5. }
  6. public StudentC() {
  7. }
  8. public StudentC(StudentA studentA) {
  9. this.studentA = studentA;
  10. }
  11. }
[html]  view plain  copy

 
 
  1. <bean id="a" class="com.zfx.student.StudentA">
  2. <constructor-arg index="0" ref="b"></constructor-arg>
  3. </bean>
  4. <bean id="b" class="com.zfx.student.StudentB">
  5. <constructor-arg index="0" ref="c"></constructor-arg>
  6. </bean>
  7. <bean id="c" class="com.zfx.student.StudentC">
  8. <constructor-arg index="0" ref="a"></constructor-arg>
  9. </bean>

下面是测试类:

[java]  view plain  copy

 
 
  1. public class Test {
  2. public static void main(String[] args) {
  3. ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml");
  4. //System.out.println(context.getBean("a", StudentA.class));
  5. }
  6. }

执行结果报错信息为:

[java]  view plain  copy

 
 
  1. Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
  2. Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

②【setter循环依赖】field属性的循环依赖【setter方式 单例,默认方式-->通过递归方法找出当前Bean所依赖的Bean,然后提前缓存【会放入Cach中】起来。通过提前暴露 -->暴露一个exposedObject用于返回提前暴露的Bean。】

setter方式注入:

图中前两步骤得知:Spring是先将Bean对象实例化【依赖无参构造函数】--->再设置对象属性的

这就不会报错了:

原因:Spring先用构造器实例化Bean对象----->将实例化结束的对象放到一个Map中,并且Spring提供获取这个未设置属性的实例化对象的引用方法。结合我们的实例来看,,当Spring实例化了StudentA、StudentB、StudentC后,紧接着会去设置对象的属性,此时StudentA依赖StudentB,就会去Map中取出存在里面的单例StudentB对象,以此类推,不会出来循环的问题喽。

3.  如何检测是否有循环依赖?how to  find?

可以 Bean在创建的时候给其打个标记,如果递归调用回来发现正在创建中的话--->即可说明循环依赖。

4.怎么解决的?  todo what?

Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或zh属性是可以延后设置的(但是构造器必须是在获取引用之前)。

Spring的单例对象的初始化主要分为三步:


    ①:createBeanInstance:实例化,其实也就是 调用对象的构造方法实例化对象

②:populateBean:填充属性,这一步主要是多bean的依赖属性进行填充

③:initializeBean:调用spring xml中的init() 方法。

从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二步。也就是构造器循环依赖和field循环依赖。

那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。

调整配置文件,将构造函数注入方式改为 属性注入方式 即可

3.源码怎么实现的? how?

(1)三级缓存源码主要 指:

  1.  
    /** Cache of singleton objects: bean name --> bean instance */
  2.  
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
  3.  
     
  4.  
    /** Cache of singleton factories: bean name --> ObjectFactory */
  5.  
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
  6.  
     
  7.  
    /** Cache of early singleton objects: bean name --> bean instance */
  8.  
    private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

这三级缓存分别指:

singletonFactories : 单例对象工厂的cache 
 earlySingletonObjects :提前暴光的单例对象的Cache 。【用于检测循环引用,与singletonFactories互斥】

singletonObjects:单例对象的cache

我们在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就就是:

  1.  
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  2.  
    Object singletonObject = this.singletonObjects.get(beanName);
  3.  
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  4.  
    synchronized (this.singletonObjects) {
  5.  
    singletonObject = this.earlySingletonObjects.get(beanName);
  6.  
    if (singletonObject == null && allowEarlyReference) {
  7.  
    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
  8.  
    if (singletonFactory != null) {
  9.  
    singletonObject = singletonFactory.getObject();
  10.  
    this.earlySingletonObjects.put(beanName, singletonObject);
  11.  
    this.singletonFactories.remove(beanName);
  12.  
    }
  13.  
    }
  14.  
    }
  15.  
    }
  16.  
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
  17.  
    }

上面的代码需要解释两个参数:

  • isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。)
  • allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象

分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:

  1.  
    this.earlySingletonObjects.put(beanName, singletonObject);
  2.  
    this.singletonFactories.remove(beanName);
  • 1
  • 2

从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。

从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义如下:

  1.  
    public interface ObjectFactory<T> {
  2.  
    T getObject() throws BeansException;
  3.  
    }
  • 1
  • 2
  • 3

这个接口在下面被引用

  1.  
    protectedvoidaddSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  2.  
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
  3.  
    synchronized (this.singletonObjects) {
  4.  
    if (!this.singletonObjects.containsKey(beanName)) {
  5.  
    this.singletonFactories.put(beanName, singletonFactory);
  6.  
    this.earlySingletonObjects.remove(beanName);
  7.  
    this.registeredSingletons.add(beanName);
  8.  
    }
  9.  
    }
  10.  
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这里就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。

知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决

最新文章

  1. 串口实现FIFO接受数据
  2. MySQL学习笔记十:日志管理
  3. winform把图片存储到数据库
  4. 未能加载文件或程序集“System.Data.SQLite.DLL”或它的某一个依赖项
  5. linux c程序中获取shell脚本输出的实现方法
  6. centos 6 7更改主机名 命令添加ip
  7. 设置ISE/vivado中默认文本编辑器为gvim
  8. Highcharts资料
  9. Android Studio学习随笔-UI线程阻塞以及优化
  10. akka 入门
  11. Docker(开课吧笔记)
  12. Redis集群功能预览
  13. [Swift]LeetCode144. 二叉树的前序遍历 | Binary Tree Preorder Traversal
  14. FastJson序列化Json自定义返回字段,普通类从spring容器中获取bean
  15. IntelliJ IDEA 下载安装(含注册码)
  16. loj#2002. 「SDOI2017」序列计数(dp 矩阵乘法)
  17. 八、IIC 接口
  18. 单个 LINQ to Entities 查询中的两个结构上不兼容的初始化过程中出现类型“XXXX”
  19. Docker技术入门与实战 第二版-学习笔记-6-仓库
  20. 情绪ABC理论

热门文章

  1. win10 VScode配置GCC(MinGW)
  2. 关于Windows安装两个不同版本的MySQL详细步骤
  3. ESP32-S3 arduino 开发环境搭建
  4. bugku ctf 杂项 旋转跳跃 (熟悉的声音中貌似又隐藏着啥,key:syclovergeek)
  5. Burp intruder暴力攻击web口令
  6. springMVC整合mybatis,spring
  7. System.Console.WriteLine() 调用原理
  8. Hadoop权威指南 - 学习笔记
  9. Java:List(二)——List、ArrayList、LinkedList
  10. 分布式多线程 EOFError: Ran out of input