在使用Spring 开发过程中,我们需要对定义后的bean 通过构造方法,或者bean 注入的方式注入到某个类中进而使用改bean 对应的方法,在此过程中就会出现一个类中注入了n多个bean,几个类中bean 相互注入,出现 A 依赖B,B依赖C,C又依赖A/B 这种循环情况的出现。
提示:以下是本篇文章正文内容,下面案例可供参考
循环依赖是指在Spring容器中,存在两个或多个Bean之间的互相依赖关系,形成了一个闭环的依赖链。具体来说,当Bean A依赖Bean B,同时Bean B又依赖Bean A时,就产生了循环依赖。
循环依赖可能会导致以下问题:
死锁:如果循环依赖的解析过程不正确,可能会导致死锁。当容器无法确定如何先实例化哪个Bean时,可能会造成死锁情况,导致应用程序无法继续正常执行。
未完全初始化的Bean:在解决循环依赖时,Spring使用了代理对象来解决依赖问题。这意味着当Bean A注入到Bean B中时,A可能是一个未完全初始化的代理对象,而不是完全实例化的对象。这可能导致在早期阶段的Bean存在一些限制和潜在的问题。
为了解决循环依赖的问题,Spring使用了一个两阶段的解析过程:实例化阶段和注入阶段。在实例化阶段,Spring创建对象并将其放入缓存中;在注入阶段,Spring解决依赖关系并完成注入。
SpringBoot 从 2.6 之前默认开启循环依赖,之后 开始默认不允许出现 Bean 循环引用,如果需要则进行手动开启:
spring:
main:
allow-circular-references:true
Spring 三级缓存 实际上使用了3个Map 来打破单例对象循环依赖的问题,singletonObjects:这是一级缓存,保存已经实例化且完成了所有的依赖注入和初始化的单例bean实例。earlySingletonObjects:这是二级缓存,保存已经实例化但尚未完成所有的依赖注入和初始化的单例bean实例。它主要用于解决属性注入时的循环依赖问题。singletonFactories:这是三级缓存,保存创建单例bean实例的ObjectFactory。当创建bean时,Spring首先会尝试从singletonFactories中获取bean实例的ObjectFactory,用于创建bean实例。一旦bean创建完成,会将其从singletonFactories中移除。
对象的生成必须先通过其构造方法进行实例化,然后对其属性赋值完成初始化;以下以 Aservice ,Bservice,Cservice 为例进行研究;
public interface Aservice {
}
public interface Bservice {
}
public interface Cservice {
}
@Service
public class AserviceImpl implements Aservice {
@Autowired
private Bservice bservice;
}
@Service
public class BserviceImpl implements Bservice {
@Autowired
private Aservice aservice;
}
@Service
public class CserviceImpl implements Cservice {
@Autowired
private Aservice aservice;
@Autowired
private Bservice bservice;
}
其中Aservice 依赖Bservice 的bean ,Bservice 依赖Aservice 的bean,Cservice 依赖Aservice ,Bservice 的bean ;
然后在从单例池中获取Aservice 的bean,发现没有Aservice 的bean ,这个时候如果重复在走Aservice 的bean 创建过程就会陷入死循环,显然我们的项目此时是可以成功启动的,也即没有陷入死循环中,那么Spring 是怎么解决的?
如果说在创建Aservice 的bean时是分为两步:
那么如果我们在步骤1 之后 ,就将还没有完成初始化的Aservice 的bean 放入到某个地方,然后在初始化其他bean 的时候 如果发现依赖了Aservice 的bean 此时可以直接注入Aservice 的bean完成对其的引用,即使Aservice 的bean还没有进行完整的初始化,我们进行了提前暴露,这样在Aservice 的bean真正完成初始化之后,对Aservice 的bean引用也随即完成;这样就打破了bean 的循环依赖,bean 可以正常初始化了;
现在Bservice,Cservice 中都依赖了Aservice 的bean ,显然无法对Aservice 的单例bean 实例化两次 ,那么就需要有个地方来存放Aservice 的这个还没有完全初始化的bean,这样后续其它的bean 在注入Aservice 的bean 时 会发现 Aservice 的bean 已经有了,所以就可以直接使用,不需要在额外创建,这里spring 使用 map (二级缓存)来存放已经实例化,但是还没有完全初始化的 bean , 以便于在发生循环依赖时,如果从单例池中获取不到对应的bean 就到二级缓存中在获取一次,如果获取到了可以直接使用,如果获取不到则需要去生成这个bean 并将其放入到二级缓存中;
因为Bservice 中已经将Aservice的bean 放入到了二级缓存中,所以Cservice 可以直接从二级缓存中获取到service 的单例bean ;
到此看起来spring 已经通过二级缓存来提前暴露未初始化完成的bean 而解决了循环依赖,那么为什么还有三级缓存的概念?
在原码中可以看到三级缓存也是一个map ,并且其value 存的是一个对象的工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
我们可能已经听说过三级缓存放入的是Lambda表达式 的匿名函数,这个函数会在使用到的时候被调用,那么spring 为什么选择放一个匿名函数而不是直接放入一个bean 呢;显然如果直接放入一个bean 那么三级缓存的作用就和二级缓存相同了;所以spring 这样做肯定是有一些原因的,先来看下在原码中三级缓存放入的Lambda是什么:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
SmartInstantiationAwareBeanPostProcessor bp;
if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
for(Iterator var5 = this.getBeanPostProcessorCache().smartInstantiationAware.iterator(); var5.hasNext(); exposedObject = bp.getEarlyBeanReference(exposedObject, beanName)) {
bp = (SmartInstantiationAwareBeanPostProcessor)var5.next();
}
}
return exposedObject;
}
从以上代码可以执行先把初始的 bean 对象赋给了 exposedObject
,然后如果发现这个bean 是否满足mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()
条件,进而判断当前的bean是否是一个合成的代理对象
。在AOP中,合成代理对象是通过特定的机制(如JDK动态代理或CGLIB动态代理)创建的。这个条件可以用来检查当前创建的bean是否是这种合成的代理对象
;
如果需要合成代理对象则 进入exposedObject = bp.getEarlyBeanReference(exposedObject, beanName) 进行代理对象的创建:
AbstractAutoProxyCreator.getEarlyBeanReference
// 省略代码
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return this.wrapIfNecessary(bean, beanName, cacheKey);
}
Object exposedObject = bean;
try {
// 递归填充改bean 的其他属性
this.populateBean(beanName, mbd, instanceWrapper);
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
} catch (Throwable var18) {
if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
throw (BeanCreationException)var18;
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
}
// 省略代码
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
} else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
} else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} else {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
} else {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
}
从上面代码可以看到 如果这个对象在缓存中没有而且是不能被跳过的 则使用this.createProxy 为其生产代理对象并进行返回
;所以使用了这个Lambda表达式目的就是返回一个普通对象的bean 或者代理对象的bean
,那么为什么不直接在bean 实例化之后,就直接调用getEarlyBeanReference 方法,这样将生成的普通对象或者代理对象的bean 直接放入到二级缓存中
,这样岂不是更为直接,显然这样做也是可以解决spring 的循环依赖的问题,而且在二级缓存中存放的对象就是普通对象或者代理生成的对象。
虽然可以这样做但是违反了spring 对代理对象生成的原则
,Spring 的设计原则是尽可能保证普通对象创建完成之后,再生成其 AOP 代理(尽可能延迟代理对象的生成)
,因为这样做的话,所有代理都提前到了实例化之后,初始化阶段前,显然与尽可能延迟代理对象的生成 原则是违背的
。所以在此使用 Lambda表达式 ,在真正需要创建对象bean 的提前引用时,才通过 Lambda表达式 来进行创建 ,来遵循尽可能延迟代理对象的生成 原则
。
没有依赖,有AOP 这种情况中,我们知道 AOP 代理对象的生成是在成品对象创建完成之后创建的,这也是 Spring 的设计原则,代理对象尽量推迟创建
,循环依赖 + AOP 这种情况中, 代理对象的生成提前了,因为必须要保证其 AOP 功能
,那么在bean 初始化完成之后,又到了要对改对象进行代理增强的环节,此时spring 又是怎么判断改bean 已经被增强为代理对象,而不需要重新创建代理对象?
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return this.wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return this.wrapIfNecessary(bean, beanName, cacheKey);
}
从以上源码中可以看到 在postProcessAfterInitialization bean 被初始化完成之后执行的方法 从this.getCacheKey 获取到的cacheKey ,最后比较两个bean 是否是一个,如果是则说明bean 已经进行过代理,否则则重新执行wrapIfNecessary 生成代理对象
;
既然在spring 容器中bean 是单例的,那么就不可能存在改bean 的多个对象,也即对bean 的所有引用都指向同一个对象;此时就有一个问题,当一个bean 被依赖注入时,怎么知道这个单例的bean 是否已经被初始化?
所以就需要将已经完成初始化的bean 放入到一个地方中,这个地方要满足如果这个bean 已经被初始化过了,则不需要进行在进行初始化,而且存和取都比较方便,spring 选用map 来对其进行存储,其中key 为bean的那么,value 为单列bean:
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
当Spring创建一个单例bean时,会先检查一级缓存(singletonObjects),如果在其中找到bean实例,则直接返回
。如果没有找到,则继续执行bean的创建流程,并将工厂方法存储在三级缓存(singletonFactories)中
。
在创建bean的过程中,如果存在循环依赖问题,Spring会先尝试从二级缓存(earlySingletonObjects)中获取bean实例的早期引用,以解决循环依赖
。如果早期引用不存在,就会使用三级缓存(singletonFactories)中的工厂方法创建bean实例
。
一旦bean创建完成,会将其放入一级缓存(singletonObjects)
,并从二级缓存(earlySingletonObjects)和三级缓存(singletonFactories)中移除
。
AbstractBeanFactory,doGetBean
获取对应的bean,此处只列举关键代码
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
/**
** 省略代码
**/
// 会先检查一级缓存(singletonObjects),如果在其中找到bean实例,则直接返回,此方法调用DefaultSingletonBeanRegistry 下getSingleton 方法
Object sharedInstance = this.getSingleton(beanName);
/**
** 省略代码
**/
// 如果没有找到,则继续执行bean的创建流程,并将工厂方法存储在三级缓存(singletonFactories)中
if (mbd.isSingleton()) {
// this.getSingleton 通过改bean 的工厂方法创建出来bean 并放入到单例池中
sharedInstance = this.getSingleton(beanName, () -> {
try {
// 创建普通对象/代理对象 并将工厂方法存储在三级缓存(singletonFactories)中
// 此方法调用 AbstractAutowireCapableBeanFactory 下 createBean 方法然后在调用 doCreateBean 方法
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throw var5;
}
});
beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
Object sharedInstance = this.getSingleton(beanName) 方法,DefaultSingletonBeanRegistry 下getSingleton
, 会先检查一级缓存(singletonObjects),如果在其中找到bean实例,则直接返回:
@Nullable
public Object getSingleton(String beanName) {
return this.getSingleton(beanName, true);
}
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从一级缓存获取完整的bean
Object singletonObject = this.singletonObjects.get(beanName);
// 没有获取到 并且 这个bean 正在初始化中
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
// 从二级缓存获取这个bean 的早期引用
singletonObject = this.earlySingletonObjects.get(beanName);
// 没有早期引用 并且运行循环依赖
if (singletonObject == null && allowEarlyReference) {
synchronized(this.singletonObjects) {
// 加对象锁
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 从三级缓存 获取bena 的 工厂类
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 二级缓存中放入
this.earlySingletonObjects.put(beanName, singletonObject);
// 三级缓存中移除
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
单例池中没有改bean 则进入 sharedInstance = this.getSingleton(beanName, () -> {}) return this.createBean(beanName, mbd, args) 方法
通过AbstractAutowireCapableBeanFactory.doCreateBean
创建普通对象/代理对象 并将工厂方法存储在三级缓存(singletonFactories)中:
boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
}
// addSingletonFactory 放入到三级缓存
this.addSingletonFactory(beanName, () -> {
// 获取提前暴露出来的bean 对象
return this.getEarlyBeanReference(beanName, mbd, bean);
});
}
try {
// 继续对改bean 中的属性进行初始化
this.populateBean(beanName, mbd, instanceWrapper);
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
} catch (Throwable var18) {
if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
throw (BeanCreationException)var18;
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
}
DefaultSingletonBeanRegistry.addSingletonFactory: bean
的工厂放入到三级缓存:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized(this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// 放入3级缓存map
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
// 将改bean 的名称放入到正在创建的bean 的set 集合
this.registeredSingletons.add(beanName);
}
}
}
在对改bean 的工厂放入到三级缓存之后,继续调用 sharedInstance = this.getSingleton(beanName, () -> {}) , this.getSingleton
方法:
DefaultSingletonBeanRegistry.getSingleton 将对应的bean 通过 Lambda表达式 生成对应的bean ,然后最终将改bean 放入到单例池中;
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized(this.singletonObjects) {
// 获取对象锁,然后从单例池中获取bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 省略代码
try {
// 获取bean 的工厂
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
// 省略代码
if (newSingleton) {
// 放入到单例翅中
this.addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized(this.singletonObjects) {
// 单例池中放入改bean
this.singletonObjects.put(beanName, singletonObject);
// 从三级缓存和二级缓存中移除
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
在A 依赖于B,B 依赖于C,C又依赖A 的场景中,当初始化 A 时,会先实例化 B,然后再实例化 C,然后再实例化 A:
Spring 使用了缓存机制,确保在递归调用时能够正确地获取到已经创建的对象实例,避免死循环
。同时,Spring 也会处理代理对象的生成和使用,以确保 A、B、C 的代理对象在正确的时间被创建和使用。
在A类中通过构造方法的方式注入B的bean,在B类中通过构造方法的方式注入A的bean;在此场景中,
@Service
public class AserviceImpl implements Aservice {
@Autowired
private Bservice bservice;
@Async
public void test(){
System.out.println(bservice);
}
}
@Service
public class BserviceImpl implements Bservice {
@Autowired
private Aservice aservice;
}
当使用 @Async 注解标注一个bean 中的方法为异步方法时,Bservice 中注入的Aservice aservice 的bean 与最终生成的Aservice 的bean 不相同而导致报错
;
延迟加载可以通过将 bean 的依赖关系运行时进行注入,而不是在初始化阶段
。这样,当遇到循环依赖时,Spring 可以先创建需要的 bean 实例,并将其设置为代理对象,而不需要立即解决依赖关系。
@Service
public class AserviceImpl implements Aservice {
@Autowired
@Lazy
private Bservice bservice;
@Async
public void test(){
}
}
Lazy 延迟加载打破循环依赖; 通过其它途径生成bservice 的lazy 的代理对象,不会去走创建bservice 的代理 对象 然后注入aservice 这套流程。这样创建aservice 的单例对象并放入到单例池中,Bservice 的bean 在实例化后,注入aservice bean 属性就可以从单例池中加载到aservice 的真正的bean ,而不会出现bean 对象不一致的问题。
spring 通过三级缓存解决单例的循环依赖问题,singletonObjects 用来存放已经初始化完成的bean,earlySingletonObjects 用来存放早期暴露出来的半成品bean 的引用,singletonFactories 用来存放获取早期引用的 Lambda表达式 工厂。
Spring的三级缓存整理;
从源码角度,带你研究什么是三级缓存;