作者并不是只有自己的观点。这篇文章是在作者看了关于spring循环依赖的文章之后总结出来的。
一般来说我们不会出现很多的循环依赖问题,但是在某些业务逻辑中导致了需要使用,还是不可避免的。比如多个service层的业务互相调用等...
针对这个spring循环依赖问题我们今天来探讨一下,如果作者写的有问题希望大家提出,我一定及时改正。
在平常我们的使用中,如果出现了循环依赖很好解决,但是在spring的ioc容器中是一个非常棘手的问题,spring在面对ioc中的循环依赖问题时进行了很多的处理来保证最终的ioc容器正常运行,期中最为关键的就是缓存的使用,那么我们来看看spring是如何巧妙的使用缓存来解决循环依赖问题吧。
何为循环依赖:
@Component
public class X {
@Autowired
private Y y;
public X() {
System.out.println("X is Created");
}
}
@Component
public class Y {
@Autowired
private X x;
public Y() {
System.out.println("Y is Created");
}
}
那么我们具体在创建的流程中是如何的呢?
我们其实在看这张图之前可以猜测一下,创建对象的过程
spring对象的创建过程:
创建对象-->创建实例-->填充属性-->初始化
那么如果我们不去解决循环依赖,很明显,他会超时,一直A获取B,B获取A,就进入了死循环。
那么spring为了解决就引入了三级缓存,
/** 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 ConcurrentHashMap<>(16);
一级缓存:也叫做单例池,也就是存储所有spring-ioc中需要单例实例化的Bean,如果你添加了@Lazy(懒加载)@Scope(prototype)(典型)等一些他是不会被ioc主动实例化到这个池中的,所以spring中的getBean方法就是从这个Map中拿实例的。
AnnotationConfigApplicationContext
a = new AnnotationConfigApplicationContext(App.class);
X x = a.getBean(X.class);
System.out.println(x);
二级缓存:初始化中的Bean,也叫做半成品池,当你出现了循环依赖的时候,比如我们生成A,需要加载B,而B又需要注入A,此时的A是没有经过上面我们写的初始化这一步,那么也就是不是一个完整的对象,我们是肯定不能在单例池里面找到的,那么这个时候我们会将A做个标记,放入二级缓存内,供B注入,然后B对象就创建好了,B就会存在于单例池中,A中的属性填充B,就可以正常填充了,这样A,B就创建好了。这也就解决了spring-ioc容器中的循环依赖问题。
那么到这里我相信读者肯定会有问题:二级换粗不是可以解决循环依赖问题吗?那么三级缓存存在的意义何在呢?
这个时候我们就要知道一件事,AOP(面向切面)中存在的动态代理技术,很多时候我们需要的Bean对象并不是直接拿到的原对象实例,而是需要通过CGlib动态代理之后生成的代理对象,那么我们就会发现,我们需要A的代理对象,属性填充中的B也是代理对象,这个时候,我们就发现二级缓存无法解决这个问题了。
所以spring因此为了解决AOP+循环依赖,则产生了三级缓存来解决,那么三级缓存是什么呢?
三级缓存==>工厂池
因为我们创建A的代理对象的时候,我们需要填充属性B,这个时候spring会先去单例池中找B,肯定是找不到的,然后去半成品池中找代理对象B,很明显,还是找不到,这个时候他就去调用工厂池中的factory(B),创建一个提前引用的B的代理对象,供A使用
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
//先看单例池中是否有已经创建好的对象
if (!this.singletonObjects.containsKey(beanName)) {
//将对象名称放入对应的工厂池
this.singletonFactories.put(beanName, singletonFactory);
//移除正在创建的对象
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
//发现是AOP的代理对象出现了循环依赖问题,需要提前引用
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
我们平常的动态代理都是在创建完对象,初始化的时候再生成对象的,而AOP这里显然不行。
这个bean工厂不仅可以暴露早期bean还可以暴露代理bean,如果存在aop代理,则依赖的应该是代理对象,而不是原始的bean。
而暴露原始bean是在单例bean初始化的第2步,之后填充属性,生成代理对象,这就矛盾了,A依赖到B并去解决B依赖时,要去初始化B,然后B又再依赖A,而此时A还没有执行代理的过程,所以,需要在填充属性前就生成A的代理并暴露出去,第二步的时候就产生代理对象就刚刚好符合我们的需求。
所以三级缓存才是所有缓存里面最值得学习的一层缓存,完美的解决了AOP代理锁带来的循环依赖问题。
如果作者哪里写的不好或者是有问题欢迎各位读者大佬来指出,我会及时改正,谢谢您的阅读!