一、 Spring发生循环依赖的原因
举个例子:
class Aservice{
Bservise bservice;
}
class Bservice{
Aservise aservice;
}
在创建Aservice时,发现需要依赖Bservice,Spring从单例池中查找Bservice,此时Bservice还未创建,则先实例化Bservice并填充Aservice属性,Spring又会去单例池中查找Aservice,由于Aservice还未创建,则实例化Aservice…一直循环下去。具体步骤如下:
1.实例化Aservice。
2.填充Aservice属性bservice。首先从单例池中查找Bservice,发现没找到,则开始创建Bservice
2.1实例化Bservice
2.2填充Bservice属性aservice。首先从单例池中查找Aservice,发现没找到,则开始创建Aservice
2.3.填充其他属性(以Aware结尾的接口)
2.4.其他重要的事情。(AOP)
2.5.将Bservice放进单例池中。
3.填充其他属性(以Aware结尾的接口)
4.其他重要的事情。
5.将Aservice放进单例池中。
二、如何解决循环依赖?
出现循环依赖的原因是2.2步骤中Bservice填充aservice属性时,从单例池中查找Aservice没找到,又开始创建Aservice,解决方法可不可以添加一个Map<String,Bean> tempMap,其中key是BeanName,value是原始的Bean对象(还未进行属性填充)。具体步骤如下:
1.实例化Aservice。放入Map<String,Bean> tempMap中。
2.填充Aservice属性bservice。首先从单例池中查找Bservice,发现没找到,则开始创建Bservice。
2.1实例化Bservice。放入Map<String,Bean> tempMap中。
2.2填充Bservice属性aservice。首先从单例池中查找Aservice,发现没找到,则从Map<String,Bean> tempMap中查找,找到了!
2.3.填充其他属性(以Aware结尾的接口)
2.4.其他重要的事情。
2.5.将Bservice放进单例池中。
3.填充其他属性(以Aware结尾的接口)
4.其他重要的事情。(AOP)
5.将Aservice放进单例池中。
问题似乎得以解决?事实上没那么简单。在aop等场景,bean中需要填充的属性是代理对象,而目前填充的是原对象,且代理对象是在步骤4中才生成的。假设Aservice开启了AOP,步骤如下:
1.实例化Aservice。将原始对象放入Map<String,Bean> tempMap中。
2.填充Aservice属性bservice。首先从单例池中查找Bservice,发现没找到,则开始创建Bservice。
2.1实例化Bservice。放入Map<String,Bean> tempMap中。
2.2填充Bservice属性aservice(需要代理对象)。首先从单例池中查找Aservice,发现没找到,则从Map<String,Bean> tempMap中查找,找到了!(但找到的是原始对象)
2.3.填充其他属性(以Aware结尾的接口)
2.4.其他重要的事情。
2.5.将Bservice放进单例池中。
3.填充其他属性(以Aware结尾的接口)
4.其他重要的事情。AOP–>此时生成代理对象
5.将Aservice放进单例池中。(将代理对象放入单例池)
这种情况的解决办法就是判断是否会发生AOP等需要填充代理对象的场景。步骤如下:
1.实例化Aservice。是否发生AOP?
是:提前发生AOP,并将代理对象放入Map<String,Bean> tempMap中。
否:将原始对象放入Map<String,Bean> tempMap中。
2.填充Aservice属性bservice。首先从单例池中查找Bservice,发现没找到,则开始创建Bservice。
2.1实例化Bservice。放入Map<String,Bean> tempMap中。
2.2填充Bservice属性aservice(需要代理对象)。首先从单例池中查找Aservice,发现没找到,则从Map<String,Bean> tempMap中查找,找到了!(找到的是代理对象)
2.3.填充其他属性(以Aware结尾的接口)
2.4.其他重要的事情。
2.5.将Bservice放进单例池中。
3.填充其他属性(以Aware结尾的接口)
4.其他重要的事情。是否已经发生过AOP?如果已发生过就不再进行AOP。
5.将Aservice放进单例池中。(将代理对象放入单例池)
问题:在发生AOP时,是否所有情况下都会进行提前的AOP?
答案:不是的,只有在发生循环依赖的情况下才会进行提前的AOP。
那么如何判断发生了循环依赖呢?加入一个Set creatingSet,存放beanName,表示正在创建中的Bean。如果在属性填充时,发生某个Bean存在creatingSet中,那么一定发生了循环依赖。步骤如下:
0.加入集合creatingSet.add(“Aservice”);
1.实例化Aservice。
2.填充Aservice属性bservice。首先从单例池中查找Bservice,发现没找到,则开始创建Bservice。
2.1实例化Bservice。放入Map<String,Bean> tempMap中。
2.2填充Bservice属性aservice(需要代理对象)。首先从单例池中查找Aservice,发现没找到。aservice正在创建中(creatingSet中存在)?是:提前发生AOP,并将代理对象放入Map<String,Bean> tempMap中。否:将原始对象放入Map<String,Bean> tempMap中。
2.3.填充其他属性(以Aware结尾的接口)
2.4.其他重要的事情。
2.5.将Bservice放进单例池中。
3.填充其他属性(以Aware结尾的接口)
4.其他重要的事情。是否已经发生过AOP?如果已发生过就不再进行AOP。
5.将Aservice放进单例池中。(将代理对象放入单例池)
6.creatingSet.remove(“Aservice”);
看似完美解决了循环依赖,但考虑以下情况:
class Aservice{
@Autowired
Bservise bservice;
@Autowired
Cservise cservice;
}
class Bservice{
@Autowired
Aservise aservice;
}
class Cservice{
@Autowired
Aservise aservice;
}
此时步骤为:
0.加入集合creatingSet.add(“Aservice”);
1.实例化Aservice。
2.填充Aservice属性bservice。首先从单例池中查找Bservice,发现没找到,则开始创建Bservice。
2.1实例化Bservice。
2.2填充Bservice属性aservice(需要代理对象)。首先从单例池中查找Aservice,发现没找到。aservice正在创建中(creatingSet中存在)?是:提前发生AOP,生成代理对象,并将代理对象放入Map<String,Bean> tempMap中。否:将原始对象放入Map<String,Bean> tempMap中。
2.3.填充其他属性(以Aware结尾的接口)
2.4.其他重要的事情。
2.5.将Bservice放进单例池中。
2.填充Aservice属性cservice。首先从单例池中查找cservice,发现没找到,则开始创建cservice。
2.1实例化cservice。
2.2填充Cservice属性aservice(需要代理对象)。首先从单例池中查找Aservice,发现没找到。aservice正在创建中(creatingSet中存在)?是:提前发生AOP,生成代理对象,并将代理对象放入Map<String,Bean> tempMap中。否:将原始对象放入Map<String,Bean> tempMap中。
2.3.填充其他属性(以Aware结尾的接口)
2.4.其他重要的事情。
2.5.将Bservice放进单例池中。
3.填充其他属性(以Aware结尾的接口)
4.其他重要的事情。是否已经发生过AOP?如果已发生过就不再进行AOP。
5.将Aservice放进单例池中。(将代理对象放入单例池)
6.creatingSet.remove(“Aservice”);
对于以上情况,Aservice发生了两次AOP,产生了两个代理对象,而Aservice是单例的,给其他Bean填充的Aservice属性应该是同一个才对。那么能不能在发生提前AOP的时候就将Aservice代理对象放入单例池,后续需要的时候直接在单例池拿的?答案是不行的,因为单例池存放的Bean一定是完整的Bean,一定要经过Spring创建Bean的全过程,但是提前发生AOP产生的代理对象是不完整的,因此不能放入单例池。
如何解决这个问题?引入二级缓存产生的代理对象放入二级缓存中。步骤如下:
0.加入集合creatingSet.add(“Aservice”);
1.实例化Aservice。
2.填充Aservice属性bservice。首先从单例池中查找Bservice,发现没找到,则开始创建Bservice。
2.1实例化Bservice。
2.2填充Bservice属性aservice。单例池–>找不到—>二级缓存—>找不到----->creatingSet—>找到了---->发生提前AOP---->产生的代理对象放入缓存池。
2.3.填充其他属性(以Aware结尾的接口)
2.4.其他重要的事情。
2.5.将Bservice放进单例池中。
2.填充Aservice属性cservice。首先从单例池中查找cservice,发现没找到,则开始创建cservice。
2.1实例化cservice。
2.2填充Cservice属性aservice(需要代理对象)。单例池–>找不到—>二级缓存—>找到了。
2.3.填充其他属性(以Aware结尾的接口)
2.4.其他重要的事情。
2.5.将Bservice放进单例池中。
3.填充其他属性(以Aware结尾的接口)
4.其他重要的事情。是否已经发生过AOP?如果已发生过就不再进行AOP。
5.将Aservice放进单例池中。从二级缓存中取出代理对象,放入单例池。
6.creatingSet.remove(“Aservice”);
通过以上方法,即可为不同的Bean注入同一个单例属性。
事实上,在发生AOP时,需要拿到原始对象,生成代理对象后,将原始对象赋值给代理对象的属性,在代理逻辑中会用到,而以上的实现方法,在发生AOP的时候,拿不到原始对象。解决方法:引入三级缓存。步骤如下:
0.加入集合creatingSet.add(“Aservice”);
1.实例化Aservice。放入三级缓存。
2.填充Aservice属性bservice。首先从单例池中查找Bservice,发现没找到,则开始创建Bservice。
2.1实例化Bservice。
2.2填充Bservice属性aservice。单例池–>找不到—>二级缓存—>找不到----->creatingSet—>找到了---->三级缓存—>拿到原始对象—>发生提前AOP---->产生的代理对象放入二级缓存。
2.3.填充其他属性(以Aware结尾的接口)
2.4.其他重要的事情。
2.5.将Bservice放进单例池中。
3.填充其他属性(以Aware结尾的接口)
4.其他重要的事情。是否已经发生过AOP?如果已发生过就不再进行AOP。
5.将Aservice放进单例池中。从二级缓存中取出代理对象,放入单例池。
6.creatingSet.remove(“Aservice”);
由于在给代理对象赋值时需要BeanName,BeanClass,原始Bean等信息,Spring的三级缓存结构为:Map<String,Function>。function参数有beanName、BeanDefinition、原始Bean。
至此Spring的三级缓存如下:
一级缓存:Map<String, Object> singletonObjects
二级缓存:Map<String, Object> earlySingletonObjects
三级缓存:Map<String, ObjectFactory<?>> singletonFactories。
以Spring的集合描述步骤如下:
0.加入集合singletonsCurrentlyInCreation.add(“Aservice”);
1.实例化Aservice。放入三级缓存singletonFactories。
2.填充Aservice属性bservice。首先从单例池singletonObjects中查找Bservice,发现没找到,则开始创建Bservice。
2.1实例化Bservice。
2.2填充Bservice属性aservice。单例池singletonObjects–>找不到—>是否发生循环依赖singletonsCurrentlyInCreation---->二级缓存earlySingletonObjects—>找不到---->三级缓存singletonFactories—>拿到原始对象—>发生提前AOP---->产生的代理对象放入二级缓存earlySingletonObjects。
2.3.填充其他属性(以Aware结尾的接口)
2.4.其他重要的事情。
2.5.将Bservice放进单例池中singletonObjects。
3.填充其他属性(以Aware结尾的接口)
4.其他重要的事情。是否已经发生过AOP?如果已发生过就不再进行AOP。
5.将Aservice放进单例池singletonObjects中。从二级缓存earlySingletonObjects中取出代理对象,放入单例池。
6.singletonsCurrentlyInCreation.remove(“Aservice”);
以下情况Spring能否帮我们解决循环依赖?
class Aservice{
Bservise bservice;
setBservice(Bservice bservice){this.bservice=bservice}
}
class Bservice{
Aservise aservice;
}
答案是不能,构造器注入发生在实例化阶段,根本走不到下面的逻辑。解决方法是加上@Lazy注解,用懒加载注入。
class Aservice{
Bservise bservice;
@Lazy
setBservice(Bservice bservice){this.bservice=bservice}
}
class Bservice{
Aservise aservice;
}