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. 定制Centos7.9镜像

    Ps:因为工作内容:有一部份是需要重装系统:系统版本镜像为centos7.9.可每次装完都需要下载一些基础包:最近因为设备过多:网卡名称太乱:导致做后续配置太繁琐:不规整:索性自己定制个系统: 搭建基 ...

  2. 使用SpringBoot整合MybatisPlus出现 : java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test

    解决方案一: 将测试类的包路径改为和主启动类的一致 解决方法二: 不想改测试类的路径 就在测试类上添加要测试的类的classes

  3. Wireshark教程之高级功能

    实验目的 1.工具介绍 2.主要应用 实验原理 1.网络管理员用来解决网络问题 2.网络安全工程师用来检测安全隐患 3.开发人员用来测试执行情况 4.学习网络协议 实验内容 1.数据流追踪功能 2.协 ...

  4. [Unity] 在软件标题栏显示工作路径

    (一)问题 项目开发中常会有开多个分支,同时启动多个 Unity 程序的情况,来回切换的时候就容易混淆,有时候还需要用 Show In Explorer 或者其他标志来确认当前使用的是哪个分支. 于是 ...

  5. Linux性能优化实战CPU篇之软中断(三)

    一.软中断 1,中断的定义 a>定义 举例:你点了一份外卖,在无法获知外卖进度的情况下,配送员送外卖是不等人的,到了发现没人取会直接走,所以你只能苦苦等着,时不时去门口看送到没有,无法干别的事情 ...

  6. Java -- next()和nextLine()的区别

    next()读取有效字符串,遇到Tab.空格.回车结束,所以不能接收带空格的字符串 nextLine()读取一行,只是以回车结束,所以可以接收带空格的字符串 https://blog.csdn.net ...

  7. centos网络配置、虚拟机克隆

    查看网卡命令: ifconfig 查看网络配置 ifconfig -a 查看隐藏网卡 window下使用ipconfig 网络配置 设置静态IP,修改/etc/sysconfig/network-sc ...

  8. Linux查看进程的4种方法

    转至:https://www.cnblogs.com/hml-blog-com/p/11558369.html 进程是在 CPU 及内存中运行的程序代码,而每个进程可以创建一个或多个进程(父子进程). ...

  9. 虚拟地址和物理地址(MMU)以及cache

    最近非常忙,博客很乱也没有更新,这里随便记录点东西,周末有空整理下. cache是一个与CPU很近的高速存储器, 作用:提高内存的访问读写速度 cache属性是指对这部分虚拟地址的读写是使用cache ...

  10. pandas模块篇(终章)及初识mataplotlib

    今日内容概要 时间序列 针对表格数据的分组与聚合操作 其他函数补充(apply) 练习题(为了加深对DataFrame操作的印象) mataplotlib画图模块 今日内容详细 时间序列处理 时间序列 ...