spring循环依赖

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战java

晚上好,我是卢卡,对于不少新手,项目中刚开始使用的框架就是spring,可是spring你到底了解多少,对于数据操做的底层对象调用,如何减小重复造轮子,这些都是咱们要掌握的问题。本期,咱们就利用spring中如何解决,依赖注入(Dependency Injection),循环依赖怎么解决;程序员

循环依赖面试

循环依赖,一般是指Bean对象之间的互相依赖,好比A对象建立须要B对象,也就是A依赖于B,依赖类推,B依赖于C,C依赖于A, 造成一个完整的闭环,如下图为:spring

多个bean之间互相依赖,造成一个闭环====>循环依赖
复制代码

说说spring容器中的循环依赖:缓存

必定是默认的单例bean中,属性互相引用的场景,也就是说 spring初始化容器方法refresh()中,对待bean,都是初始化后的单例对象==>也就是scope=singleton
复制代码

每一个对象是单例的对象,互相依赖的时候,能够直接调用,markdown

spring底层怎么保证AB循环依赖的问题?app

因此说,对于循环屡次依赖,咱们要求的是,单例且set注入------>能够避免BeanCurrentlyInCreationException的问题框架

spring循环依赖的注入方式oop

对于spring循环依赖中,有一个误区,一般能够分为构造器注入和set方法注入,两种注入方式会有影响,咱们来找寻官网中的理解;
复制代码

实例化构造器注入带来的beancurrentlyIncreationException的问题;模拟A和B两个实例对象,在循环依赖中,利用构造器互相注入,看可否实现循环依赖:post

建立三个类:

package com.atguowang.thirdinterview.spring.aroudyilai.constructor;

import org.springframework.stereotype.Component;

/**

  • @author shkstart
  • @create 2020-11-12 16:51*/@Componentpublic class ServiceA {
  • private ServiceB serviceB;
  • public ServiceA(ServiceB serviceB) {//当前私有的serviceB--this.serviceB=serviceB;}

}

package com.atguowang.thirdinterview.spring.aroudyilai.constructor;

import org.springframework.stereotype.Component;

/**

  • @author shkstart
  • @create 2020-11-12 16:51*/@Componentpublic class ServiceB {
  • private ServiceA serviceA;
  • public ServiceB(ServiceA serviceA ) {this.serviceA=serviceA;}}

package com.atguowang.thirdinterview.spring.aroudyilai.constructor;

import sun.applet.Main;

/**

  • @author shkstart
  • @create 2020-11-12 16:51*/public class TestConstructor {public static void main(String[] args) {
  • }

}

结论:

由于咱们要建立一个A,就要在构造器加载过程当中,加入一个B,可是又 要重复建立A,和B;constructor没法解决这个问题;

2:利用set注入进行spring的循环依赖注入package com.atguowang.thirdinterview.spring.aroudyilai.depencyInjection.SetInjection;

import org.springframework.stereotype.Component;

/**

  • @author lucas
  • @create 2020-12-12 17:13*/@Componentpublic class ServiceBB {
  • private ServiceAA serviceAA;
  • public void setServiceAA(ServiceAA serviceAA){this.serviceAA=serviceAA;System.out.println("B中获得这个方法A的赋值");}}

package com.atguowang.thirdinterview.spring.aroudyilai.depencyInjection.SetInjection;

import org.springframework.stereotype.Component;

/**

  • @author lucas
  • @create 2020-12-12 17:13*/@Componentpublic class ServiceBB {
  • private ServiceAA serviceAA;
  • public void setServiceAA(ServiceAA serviceAA){this.serviceAA=serviceAA;System.out.println("B中获得这个方法A的赋值");}}

循环依赖set方法注入,能够依赖实现;

package com.atguowang.thirdinterview.spring.aroudyilai.depencyInjection.SetInjection;

import sun.java2d.pipe.AAShapePipe;

/**

  • @author lucas
  • @create 2020-12-12 17:13*/public class TestSetInjection {
  • public static void main(String[] args) {/*** 测试set方法注入的循环依赖*/ServiceAA aa = new ServiceAA();ServiceBB bb = new ServiceBB();
  • }

}

上述是基于JAVA SE实现的代码,可是咱们要寻找底层spring容器中,到底对于循环依赖怎么实现呢?

 咱们先肯定一个概念,就是spring容器化;
复制代码

spring容器化

能够把spring容器化,也就是存放bean对象,(实例化对象)能够相互复用的一个池化思想,举个例子,我每次要去买鱼和鸡肉,可是这个是俩对象,存在于两个菜市场,spring容器就做为中间点,把鱼肉和鸡肉都放入里面, 而后我要使用的时候,就从池子中直接取用,这样的好处是:
复制代码

减小通信的资源损耗

节省对象复用

更好的控制对象

建立符合要求的初始化对象

spring做为一个优秀的框架,极大程度上对于程序员来讲,减小了建立对象,集中管理对象,复用等问题,作出了很大的贡献,减小了重复造轮子,也就是说,咱们要建立一个对象的时候 ,咱们不用new ,而是经过权限控制,IOC(Inversion of Control)是指容器控制程序对象之间的关系,而不是传统实现中,由程序代码直接操控。控制权由应用代码中转到了外部容器,控制权的转移给spring容器,更好的集中控制和管理对象;
复制代码

咱们如今开始觊觎spring容器化来实现循环依赖到底怎么玩?

这里咱们就利用xml文件来实现,application.xml,两个对象A和B,互相依赖
复制代码

对象A:

package com.atguowang.thirdinterview.spring.aroudyilai.depencyInjection.springIOC;

/**

  • @author lucas

  • @description spring容器中关于循环依赖的实现*/public class A {

  • private B b;

  • public B getB() {return b;}

  • public void setB(B b) {this.b = b;}

  • A(){System.out.println(" A -created- success");}

}

对象B:

package com.atguowang.thirdinterview.spring.aroudyilai.depencyInjection.springIOC;

public class B {

private A a;

public A getA() {
    return a;
}

public void setA(A a) {
    this.a = a;
}

B(){

    System.out.println(" B -created- success");
}
复制代码

}

package com.atguowang.thirdinterview.spring.aroudyilai.depencyInjection.springIOC;

/**

  • @author Lucas

  • @description set方法实现*/

public class B {

private A a;

public A getA() {
    return a;
}

public void setA(A a) {
    this.a = a;
}

B(){

    System.out.println(" B -created- success");
}
复制代码

}

建议一下,set方式注入是没问题的,而后咱们在基础上添加spring容器的配置文件,注意看好添加的位置;

application.xml:

开始作spring容器实例化建立两个对象,进行循环依赖;

package com.atguowang.thirdinterview.spring.aroudyilai.depencyInjection.springIOC;

import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;

/**

  • @author Lucas
  • @description spring单例容器化实现循环依赖*/public class ClientSpringcontainer {public static void main(String[] args) {
  • }}
  • 注意事项:
  • 第一次开始的时候,可能会碰见bean不存在,注意创建时候配置文件的位置,检查A和B的位置,我准备了连接,给你谈谈路,
  • 如何优雅的解决:

winterchen.blog.csdn.net/article/det…

思考一下,是由于spring容器化对象的时候,scope=singleton,单例对象,要是变为scope=phototype ,(原型)也就是每次调用,都会建立一个新的A对象或者是B对象, 好汉们:试一把:咱们将配置文件的scope属性改了;
复制代码

结果如图所示:

报出的错误,和咱们以前构造器注入方式一致,都是BeanCurrentlyInCreationException:bean对象一致处于建立内层的循环中,发生异常,也就说当scope属性不是单例的时候,没法解决spring循环依赖的问题;

其实作到这里,咱们只是知道了,spring循环依赖是利用单例spring容器化,或者是set方式注入,实现具体的循环依赖(circular dependencies ),可是我仍是想知道,到底源码底层是如何实现的;相应的我查到了新名词,与spring循环依赖是有三级缓存实现的;



结论先记下:spring内部是根据三级缓存来实现循环依赖的;
复制代码

三级缓存

当面试官说道,spring循环依赖,你能提到三级缓存,那证实你绝对是拥有内功的,这属于源码范畴,也能够侧面了解到你的学习潜力,哈哈,开干:三级缓存主要是这个类; DefaultSingletonBeanRegistry;
复制代码

IDEA的同窗呢,能够那两次shift键,查询这个类的具体实现过程;

这个类中实际上是利用三个MAP,来实现三级缓存的;

 何为三级缓存:
复制代码

package org.springframework.beans.factory.support;public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {protected static final Object NULL_OBJECT = new Object();protected final Log logger = LogFactory.getLog(this.getClass());private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256); //一级 单例-成品private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16); //三级--工厂beanprivate final Map<String, Object> earlySingletonObjects = new HashMap(16); //二级 半成品

三级缓存只是适用于,对象是单例bean对象,每次建立bean对象;

注意事项:

在java中对于实例化和初始化是两个概念:

    实例化对象以后-->才能初始化对象属性



 实例化:  至关于自身须要请求一块区域,可是只是在申请成功的过程当中,其中放什么仍是未知的数据



 初始化:咱们常说初始化--表明程序加载属性,各类数据,开始运行的过程
复制代码

scope=phototype

关于为何scope=phototype时候,为何会解决不了循环依赖?

原型对象-->多例模式,每次得到bean都会生成一个新的对象
复制代码

解答:

对于每次建立一个对象bean,不是单例对象,因此不会进入三级缓存,也就不能经过三级缓存--->解决循环依赖问题

理解:

由于咱们第一次建立的对象是基于单例的,全部单例的对象走完了完整的生命周期--进入缓存--而后才开始能够解决循环依赖

因为photoType每次要从新建立一个对象,因此没法放入缓存中,也就不能使用三级缓存来解决循环依赖

写到这里,关于spring循环的问题就告一段落,其实还有下篇,集中对于如何对象bean实现三级缓存的细节,咱们来处理循环依赖,下节咱们接着干,但愿你会有所收获,咱们一块儿进步,相互成就才是最好的状态。

你们晚安,美梦,我是卢卡,感兴趣就点个赞,再走吧

相关文章
相关标签/搜索