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. 开源GenICam项目上手-1

    GenICam 说明 一个统一的编程规则,这样我们只需要一个应用软件,就可以支持符合标准的不同型号相机,当我们升级相机.更换相机时,不需要编写不同的软件代码. The goal of GenICamT ...

  2. 教你快速区分传统报表和商业智能BI

    很多人分不清楚,传统报表和商业智能BI之间的区别?有些人认为,BI就是做报表的,其实不然,报表只是BI的一部分,报表是关于过去和现状的展示,而BI是关于如何通过分析数据,帮助决策者找到改变和提高的方案 ...

  3. SQL:查询时给表起别名

    Q 有两个表student.score,查询前一个表的id.name列,后一个表的total列,查询结果通过两个表的id连接起来 要求 使用别名st和sc替换表名进行查询 A SELECT st.id ...

  4. 2020.10.6 ThreadLocal

    在多线程环境下,每个线程都有自己的数据.一个线程使用自己的局部变量比使用全局变量要好,因为局部变量不会被其他线程改变. 但是局部变量也存在问题--在函数调用的时候,传递起来很麻烦: def proce ...

  5. 数据库连接池与SQL工具类

    数据库连接池与SQL工具类 1.数据库连接池 依赖包 pymysql dbutils # -*- coding: utf-8 -*- ''' @Time : 2021/11/19 16:45 @Aut ...

  6. 面试官:Redis中哈希数据类型的内部实现方式是什么?

    面试官:Redis中基本的数据类型有哪些? 我:Redis的基本数据类型有:字符串(string).哈希(hash).列表(list).集合(set).有序集合(zset). 面试官:哈希数据类型的内 ...

  7. .NET6: 开发基于WPF的摩登三维工业软件 (8) - MVVM

    基于WPF开发界面的一个很大优势是可以方便地基于MVVM设计模式开发应用.本文从应用的角度基于MVVM实现参数化管材的创建界面. 1 MVVM MVVM是Model-View-ViewModel的简写 ...

  8. 【漏洞复现】Paraluni 安全事件分析及复现

    Paraluni 被黑分析 前言 Paraluni (平行宇宙)是新加坡 Parallel Universe 基金会发布的一个 基于币安智能链的 DeFi 项目,更多相关内容见此处.在 2022 年 ...

  9. 怎么做 HDFS 的原地平滑缩容?

    背景 当数据规模越来越大,存储成本也水涨船高.随着时间推移,数据热度分布往往呈 2⁄8 原则,即 80% 的访问集中在 20% 的数据上.对于那不经常访问的 80% 数据来说,使用多个 SSD 来存储 ...

  10. python 生产数据表脚本

    # -*- coding: utf-8 -*-import re"""建立相关表的字段从源表创建指定的MySQL建表脚本"""# 目标表名称 ...