-
什么是依赖
有两个类 ClassA、ClassB,如果ClassA中有一个ClassB类型的属性或者ClassA的构造方法有一个参数类型是ClassB,就表示ClassA依赖于ClassB/** * ClassA类 */ public class ClassA { private ClassB classB; public ClassA(ClassB classB){ this.classB = classB; } public ClassB getClassB() { return classB; } public void setClassB(ClassB classB) { this.classB = classB; } } /** * ClassB类 */ public class ClassB { }
从上面的情况来看,依赖又分为构造方法依赖和属性依赖。我们称这种只是单方面依赖的情况为单向依赖吧。在构造方法依赖中创建ClassA对象时ClassB类的对象必须先存在。
-
什么是循环依赖
两种情况:- 属性依赖
如果此时ClassB类中也有一个ClassA类型的属性,就称为ClassA类和ClassB类循环依赖了。
此时还是先创建ClassB对象,再创建ClassA对象,没有问题。/** * ClassB类 */ public class ClassB { private ClassA classA; public ClassA getClassA() { return classA; } public void setClassA(ClassA classA) { this.classA = classA; } }
2. 构造方法依赖/** * ClassB类 */ public class ClassB { private ClassA classA; public ClassB(ClassA classA){ this.classA = classA; } public ClassA getClassA() { return classA; } public void setClassA(ClassA classA) { this.classA = classA; } }
上面这种情况,在创建ClassA的对象之前,要先创建ClassB的对象,而要创建ClassB的对象又必须要先创建ClassA的对象。这样就出现了死循环了。
所以这种在整条依赖链中,全是构造方法依赖产生的循环依赖,没办法解决,我们不考虑这种情况。
上面是普通对象循环依赖的情况。 - 属性依赖
-
什么是Spring的循环依赖
我们知道,从Spring容器中Bean的生命周期来看,Bean在实例化后需要对其属性进行填充,才能成为最终能使用的Bean。那么,在填充属性时也会出现依赖其他Bean对象的情况。这时就出现了和普通对象一样的相互依赖的情况。同样的,单向依赖和全依赖链路都是构造方法依赖的情况我们不考虑。原型模式创建的Bean也不考虑,这种Bean没有循环依赖的问题。
全依赖链路都是构造方法这种情况在spring中也无法解决。
还是以上面的ClassA、ClassB 两个类为例子:先来看属性依赖的情况
/** * ClassA类 */ @Component public class ClassA { @Autowired private ClassB classB; } /** * ClassB类 */ @Component public class ClassB { @Autowired private ClassA classA; } /** * 扫描配置类 */ @ComponentScan public class Config { } /** * 测试类 */ public class Main { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(Config.class); ClassA classA = context.getBean(ClassA.class); ClassB classB = context.getBean(ClassB.class); System.out.println(classA); System.out.println(classB); System.out.println(classA.getClassB()); System.out.println(classB.getClassA()); } }
再修改一下ClassA类,让它从构造方法依赖ClassB类。注意在ClassA类上添加了@DependsOn注解,意思是ClassA类的Bean对象要在classB这个Bean存在后才创建。这就类似于我们上面说的普通对象有构造方法依赖时,必须先创建被依赖的这个对象。
/** * ClassA类 */ @Component @DependsOn("classB") public class ClassA { private ClassB classB; @Autowired public ClassA(ClassB classB){ this.classB = classB; } public ClassB getClassB(){ return this.classB; } }
上面的两种情况都能正常运行
com.spring.hello.ClassA@2002fc1d com.spring.hello.ClassB@69453e37 com.spring.hello.ClassB@69453e37 com.spring.hello.ClassA@2002fc1d
思考这样一个问题,ClassA类的Bean在属性注入时需要一个ClassB类的Bean,此时Spring容器就去创建ClassB类的Bean。发现要创建ClassB类的Bean又要先创建ClassA类的Bean。这不就死循环了吗?
再看看我们上面说的普通对象的循环依赖时,直接在需要的地方使用未设置过属性的对象,然后再来设置自己的属性。ClassA classA = new ClassA(); //实例化classA ClassB classB = new ClassB(); //实例化classB classB.setClassA(classA); classA.setClassB(classB);
我们解释下上面的过程,现在我们要一个ClassA类的对象,于是就去创建classA对象,发现该对象有一个ClassB类型的属性,如果我们要使用一个完整的classA对象,必须得把该属性赋值,不然就是null。然后我们就转去创建ClassB类的对象classB,发现该对象有一个ClassA类型的属性,也是为了得到一个完整的classB对象,我们得给这个属性赋值。在此之前我们已经创建了一个classA对象,虽然还不是完整的(属性是null),直接拿来用吧,把它赋值给classB对象的属性,这是没有问题的。这里基于这样一个事实,classA对象在属性赋值之前和赋值之后是同一个对象。
那么,现在classB对象已经创建完整了,再接着classA对象的属性赋值,把classB对象赋值过去。此时两个对象都创建完整了。
当然,上面的例子是因为我们知道这两个对象循环依赖了,所以按照上面的步骤控制了两个对象的创建流程。但是在spring容器创建ClassA类的Bean的时候,发现要注入一个ClassB类型的属性,于是转去创建ClassB类型的Bean,实例化后发现依赖了一个ClassA类型的属性,这时候Spring应该有一种机制能记得之前已经创建过ClassA的Bean了,虽然是不完整的。这种记忆功能在Spring中是通过缓存来实现的,而且是三级缓存。我们接着往下看。 -
Spring三级缓存
Spring使用了缓存来实现了记忆功能。先来看看三级缓存长什么样,从上面的 context.getBean(ClassA.class) 这个方法一直跟进去会经历一系列的方法,最终会走到 DefaultSingletonBeanRegistry类的getSingleton()方法中,看一下这个方法protected Object getSingleton(String beanName, boolean allowEarlyReference) { //先从一级缓存取 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { //再从二级缓存取 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { //再从三级缓存取 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
方法中用到了三个缓存,定义在类的开头
// 一级缓存 /** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 三级缓存 /** Cache of singleton factories: bean name to ObjectFactory. */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 二级缓存 /** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
为什么要用三级缓存呢?
按照我们上面的说法,实现一个记忆功能,一级缓存就够了吧。如果只用一级缓存,考虑这样一种情况,我们把属性未赋值的BeanA放入 singletonObjects 中,刚好这时有其他地方来获取这个Bean,那不是拿到了一个属性为null的Bean吗?Spring作为一个优秀的框架肯定是考虑了这一点的,所以一级缓存是存储完整的Bean的。
那用二级缓存呢?可以实现,把属性未赋值的BeanA放入earlySingletonObjects ,创建BeanB时从二级缓存取,BeanB可以创建完整,然后放入一级缓存中。再回头来给BeanA属性赋值,从一级缓存就能拿到了,BeanA也创建完整了,完美!
那么第三级的缓存用来干什么呢?既然用到了肯定是有用处的,考虑下这种情况,如果BeanA对象中有AOP实现的注解方法,比如 @Transactional,这时注入到BeanB的对象应该是BeanA对象的代理对象BeanAPxy。这种情况Spring是怎么解决的呢,我看到很多文章上都有提到下面的说法
Spring有两个选择:
1. 不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。
2. 不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况,Bean就可以按着Spring设计原则的步骤来创建。
Spring选择了第二种方式,那怎么做到提前曝光对象而又不生成代理呢?
Spring就是在对象外面包一层ObjectFactory,提前曝光的是ObjectFactory对象,在被注入时才在ObjectFactory.getObject方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存Map<String, Object> earlySingletonObjects。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
所以,按照上面的描述我们总结三级缓存的使用:
1). singletonObjects : 缓存初始化完成的Bean,即经历过实例化 -> 属性注入->初始化 步骤的bean。
2). earlySingletonObjects : 缓存提前暴露的原始bean对象或者代理对象,属性还未填充。
3). singletonFactories : 缓存对象的工厂,这个工厂可能会返回一个原始的bean,如果对象上使用了AOP切面,则返回这个对象的代理对象。
那么,三个缓存的使用就应该是这样子:
1). Spring容器先创建了BeanA的实例,然后把该对象的ObjectFactory对象放入三级缓存中,这时候发现需要一个BeanB的属性对象,找遍三级缓存都没有,于是转去创建BeanB。
2). BeanB对象实例化后,也会把该对象的ObjectFactory对象放入三级缓存中,BeanB又依赖BeanA。在第三级缓存中找到了BeanA的ObjectFactory,就调用该ObjectFactory的getObject()方法得到原始的BeanA对象(如果BeanA使用了AOP切面,则得到的是BeanA的代理对象),将其称为earlyBeanA。此时BeanA的ObjectFactory从三级缓存移除,并将earlyBeanA放入二级缓存中。BeanB拿到了earlyBeanA,完成了属性注入和后续的初始化过程,成为一个完整的Bean。然后就把这个BeanB放入到一级缓存中,并且从三级和二级缓存中移除。
3). 此时再回头来给BeanA注入属性,发现一级缓存中已经有完整的BeanB对象,于是BeanA的属性也注入成功,然后也访问一级缓存中,并且从三级和二级缓存中移除。
以上,是我对Spring循环依赖和三级缓存的理解,有错误之处,请指正!!