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三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决

SpringBean的生命周期 以及一些问题总结的更多相关文章

  1. 一步步实现:springbean的生命周期测试代码

    转载. https://blog.csdn.net/baidu_37107022/article/details/76552052 1. 创建实体SpringBean public class Spr ...

  2. 面试刷题30:SpringBean的生命周期?

    spring是Java软件开发的事实标准. 我是李福春,我在准备面试,今天的问题是:springBean的生命周期是怎样的? 答:spring最基础的能力是IOC(依赖注入),AOP(面向切面编程), ...

  3. SpringBean的生命周期以及循环依赖过程

    上面就是springBean的大致生命周期. Bean的创建过程 创建Bean之前会调用Bean工厂的后置处理器,可以获取到BeanDefinition Bean的初始化过程 初始化之前会调用前置处理 ...

  4. spring-bean(全生命周期)

    作用:在初始化和销毁bean时候,做一些处理工作是调用生命周期方法 格式: <bean id=”该生命周期的名称” class=”提供方法的类的全路径” init-methood=”init” ...

  5. springbean的生命周期

    1.Spring对Bean进行实例化(相当于程序中的new Xx())2.Spring将值和Bean的引用注入进Bean对应的属性中3.如果Bean实现了BeanNameAware接口,Spring将 ...

  6. 深入源码理解SpringBean生命周期

    概述 本文描述下Spring的实例化.初始化.销毁,整个SpringBean生命周期,聊一聊BeanPostProcessor的回调时机.Aware方法的回调时机.初始化方法的回调及其顺序.销毁方法的 ...

  7. Bean的生命周期

    Bean的生命周期 原文:http://997004049-qq-com.iteye.com/blog/1729793 任何一个事物都有自己的生命周期,生命的开始.生命中.生命结束.大家最熟悉的应该是 ...

  8. Spring Framework核心概念之Bean生命周期管理

    目录 Spring Bean的生命周期 相关接口的分类 测试SpringBean生命周期的Demo程序 小结 Spring Bean的生命周期 Spring容器既Application或者WebApp ...

  9. Spring的Bean生命周期理解

    首先,在经历过很多次的面试之后,一直不能很好的叙述关于springbean的生命周期这个概念.今日对于springBean的生命周期进行一个总结. 一.springBean的生命周期: 如下图所示: ...

随机推荐

  1. 40多个丰富的QQ特效代码,非常实用哦!

    Hi,我们好,我是GG!微信和QQ是现在干流的两款社交东西.有人说微信现已完全替代了QQ,现已没有人玩QQ了.可是小雨却不这么以为,毕竟微信和QQ的用户集体是不一样的,它们在功能上的定位也是不一样的. ...

  2. GAN实战笔记——第四章深度卷积生成对抗网络(DCGAN)

    深度卷积生成对抗网络(DCGAN) 我们在第3章实现了一个GAN,其生成器和判别器是具有单个隐藏层的简单前馈神经网络.尽管很简单,但GAN的生成器充分训练后得到的手写数字图像的真实性有些还是很具说服力 ...

  3. C#控制树莓派入门

    何为树莓派 许久没有写博客了,十二月份西安疫情的影响,居家隔离了一个多月,在其期间,学习了一下树莓派,觉得硬件还是挺有意思的,刚好也看到了巨硬有提供使用c#用来开发树莓派应用的解决方案叫Net Iot ...

  4. 拥有自助式BI要摒弃传统BI?

    简单来说BI就是从data中提取知识和信息的一套软件解决方案.商业智能 (BI,Business Intelligence) 也就是BI,是为企业把数据转换为信息.知识 ,相应蕴育而出的IT技术.企业 ...

  5. Smartbi大数据在金融业的应用案例

    我们平时听说的商业智能其实就是BI分析,它是一种提高企业智能化的手段和工具,既可以满足企业发展的需要,而且也可提高企业竞争力.思迈特软件Smartbi作为数据分析系统,受到了不少金融业客户的青睐.今天 ...

  6. (转)oracle 数据库性能健康检查脚本

    转至:https://blog.csdn.net/cm_0205/article/details/100210526?utm_medium=distribute.pc_relevant_downloa ...

  7. Java:安装新版本Java、环境配置

    最新版2021年版 Java安装目录 2.在系统变量中设置2项属性,JAVA_HOME.PATH(大小写无所谓),若已存在这点击编辑,不存在则新建 参数为: JAVA_HOME:   D:\Java\ ...

  8. Windows下Apache服务多个端口反向代理配置

    修改\Apache24\conf\httpd.conf: 1.修改安装包地址: Define SRVROOT "/Apache24" 修改为: Define SRVROOT &qu ...

  9. Redis-基本概念、java操作redis、springboot整合redis,分布式缓存,分布式session管理等

    NoSQL的引言 Redis数据库相关指令 Redis持久化相关机制 SpringBoot操作Redis Redis分布式缓存实现 Resis中主从复制架构和哨兵机制 Redis集群搭建 Redis实 ...

  10. VirtualBox 共享文件夹设置

    1. 安装VirtualBox的增强功能 2. 设置共享文件夹 参考:VirtualBox主机与虚拟机文件夹共享