循环依赖
循环依赖其实就是循环引用,即两个或者两个以上的bean互相持有对方,最终形成闭环
两大场景
第一种:构造器注入循环依赖
@Service
public class AService {
public AService(BService bService) { }
}
@Service
public class BService {
public BService(AService aService) { }
}
结果:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| AService defined in file [D:\Java\springboot\springboot\target\classes\top\parak\depend\AService.class]
↑ ↓
| BService defined in file [D:\Java\springboot\springboot\target\classes\top\parak\depend\BService.class]
└─────┘
Spring解决循环依赖依靠的是Bean的中间态,而这个中间态是指已经实例化,但未初始化。构造器执行是在中间态之前,因此构造器的循环依赖无法解决。
第二种:单例的setter注入
@Service
public class AService {
@Autowired
private BService bService;
}
@Service
public class BService {
@Autowired
private AService aService;
}
这种场景经常使用,没有问题。
第三种:多例的setter注入
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class AService {
@Autowired
private BService bService;
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class BService {
@Autowired
private AService aService;
}
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Component
public class CService {
@Autowired
private AService aService;
}
结果:
The dependencies of some of the beans in the application context form a cycle:
c (field private top.parak.depend.AService top.parak.depend.C.aService)
┌─────┐
| AService (field private top.parak.depend.BService top.parak.depend.AService.bService)
↑ ↓
| BService (field private top.parak.depend.AService top.parak.depend.BService.aService)
└─────┘
没有使用缓存,每次都会生成一个新对象。
总结
- 构造器注入和prototype类型的field注入发生循环依赖时都无法初始化
- field注入单例的bean时,尽管有循环依赖,但是bean可以成功初始化
如何检测
Bean在创建的时候可以给该Bean打个标志,如果递归调用回来发现正在创建中的话,即说明产生了循环依赖。
如何解决
DefaultSingletonBeanRegistry
内部属性:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/** 一级缓存 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 三级缓存 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** 二级缓存 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** 保存所有已经注册的bean的名字 */
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
/** 标识指定名字的bean对象是否处于创建状态 */
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
// ....
}
三级缓存:
缓存 | 级别 | 用途 |
---|---|---|
singletonObjects | 一级 | 存放最终单例,key为bean名称,value为bean实例。这里的bean实例指的是已经完全创建好的,即已经经历实例化->属性填充->初始化->后置处理过程的bean,可以直接使用。 |
earlySingletonObjects | 二级 | 存放早期对象,key为bean名称,value为bean实例。这里的bean实例指的是仅完成实例化的bean,还未进行属性填充等后续操作。用于提前曝光,供别的bean引用,解决循环依赖。 |
singletonFactories | 三级 | 存放回调方法,key为bean名称,value为bean工厂。在bean实例化之后,属性填充之前,如果允许提前曝光,Spring会把该bean转换为bean工厂并加入到三级缓存。在需要引用提前曝光对象时再通过工厂对象的getObject方法获取。 |
源码分析
IOC容器获取bean的入口为AbstractBeanFactory
的getBean
方法:
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
// 真正实现
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
String beanName = transformedBeanName(name);
Object bean;
// 先去获取一次,如果不为null,此处就会走缓存
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 不为空,则进行后续处理并返回
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
...
try {
...
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
...
// 从三级缓存中没有获取到Bean实例,并且目标Bean是单实例Bean的话
if (mbd.isSingleton()) {
// 通过getSingleton方法创建Bean实例
sharedInstance = getSingleton(beanName, () -> {
try {
// 创建Bean实例
return createBean(beanName, mbd, args);
}
});
// 后续处理,并返回
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
...
}
}
return (T) bean;
}
}
首先通过getSingleton(String beanName)
方法从三级缓存中获取bean实例,如果不为空则进行后续处理;如果为空,则通过getSingleton(String beanName, ObjectFactory<?) singletonFactory
方法创建bean实例并进行后续处理。
从三级缓存中获取bean实例的源码如下:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
// 这里是经典的DCL(双重检查锁)
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从一级缓存中获取bean
Object singletonObject = this.singletonObjects.get(beanName);
// 一级缓存中的bean为空,且当前bean正在创建
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 加锁
synchronized (this.singletonObjects) {
// 从二级缓存中获取bean
singletonObject = this.earlySingletonObjects.get(beanName);
// 二级缓存中的bean为空,且允许提前创建
if (singletonObject == null && allowEarlyReference) {
// 从三级缓存中获取对应的ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// ObjectFactory不为空
if (singletonFactory != null) {
// 从单例工厂中获取bean
singletonObject = singletonFactory.getObject();
// 添加到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
// 从三级缓存中删除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
...
}
主要流程:
- 首先,尝试从一级缓存中
singletonObjects
中获取单例Bean - 获取不到,则从二级缓存
earlySingletonObjects
中获取单例Bean - 获取不到,则从三级缓存
singletonFactories
中获取单例ObjectFactory
- 如果从三级缓存中获取成功,则将
ObjectFactory
中的object
取出放入到二级缓存中,并将ObjectFactory
从三级缓存中移除
如果通过三级缓存的查找都没有找到目标bean实例,则通过getSingleton(String beanName, ObjectFactory<?> singleton)
方法创建:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
...
// 添加互斥锁
synchronized (this.singletonObjects) {
// 从一级缓存获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 为空则继续
...
// 将当前bean名称添加到正在创建bean的集合singletonsCurrentlyInCreation
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
...
try {
// 通过函数式接口创建bean实例,该实例已经经历实例化、属性填充、初始化和后置处理,可直接使用
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// 判断在此期间是否隐式出现了单例对象
...
}
finally {
// 将当前bean名称从正在创建的集合singletonsCurrentlyInCreation中移除
afterSingletonCreation(beanName);
}
if (newSingleton) {
// 添加到缓存中
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
protected void addSingleton(String beanName, Object singletonObject) {
// 加锁
synchronized (this.singletonObjects) {
// 添加到一级缓存
this.singletonObjects.put(beanName, singletonObject);
// 删除对应的二级缓存
this.singletonFactories.remove(beanName);
// 删除对应的三级缓存
this.earlySingletonObjects.remove(beanName);
// 添加到已注册单例
this.registeredSingletons.add(beanName);
}
}
重点关注singletonFactory.getObject()
,singletonFactory
是一个函数式接口,对应AbstractBeanFactory
的doGetBean
方法中的lambda表达式:
sharedInstance = getSingleton(beanName, () -> {
try {
// 创建bean实例
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
重点关注createBean
方法,该方法为抽象方法,由AbstractBeanFactory
子类AbstractAutowireCapableBeanFactory
实现:
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
try {
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
}
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// 实例化bean
BeanWrapper instanceWrapper = null;
...
Object bean = instanceWrapper.getWrappedInstance();
...
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
// 执行MergedBeanDefinitionPostProcessor类型后置处理器
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
...
mbd.postProcessed = true;
}
}
// 如果该bean是单例,并且允许循环依赖的出现以及该bean正在创建中
// 那么就标识允许单例bean提前暴露原始对象引用
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 添加到单实例集合中,即三级缓存对象,该方法第二个参数类型为ObjectFactory函数式接口
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 初始化bean
Object exposedObject = bean;
try {
// 属性赋值
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
// 允许bean提前暴露
if (earlySingletonExposure) {
// 第二个参数为false标识仅从一级和二级缓存中获取bean单例
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
// 如果从一级和二级缓存中获取到bean实例为空
exposedObject = earlySingletonReference;
}
...
}
}
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
...
return exposedObject;
}
其中addSingletonFactory
方法在父类DefaultSingletonBeanRegistry
已实现:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
// 添加互斥锁
synchronized (this.singletonObjects) {
// 如果一级缓存没有,那么添加到三级缓存
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
整个流程总结如下:
面试逼问
(1)只用一级缓存是否可以解决循环依赖?
可以解决。
对于一级缓存,我们不等对象初始化完成之后再存入缓存,而是等对象实例化完成后就提前暴露,存入一级缓存,由于此时缓存对象并没有进行初始化操作,即早期对象。那么A实例化完成提前暴露早期对象,A在属性注入时,发现B不在容器中,也没有提前暴露,那么去进行B的生命周期,B实例化后进行属性注入,发现A提前暴露,那么B可以从一级缓存中获取到A的早期对象完成初始化,回到A的生命周期从而A也可以完成初始化。 但是这样会引发另一个问题:早期对象和完整对象都存在一级缓存中,如果此时来了其他线程并发获取bean,就可能从一级缓存中获取到bean的早期对象,这样明显不行,那么我们不得已在从一级缓存获取对象处加一个互斥锁,以避免这个问题。 加互斥锁代理来另一个问题:容器刷新完成后的普通获取bean的请求都需要竞争锁,如果这样处理,在高并发场景下使用Spring的性能必然降低。
(2)加上二级缓存有什么好处?
优化一级循环加锁带来的性能问题。
将对象的早期对象存入二级缓存中,一级缓存用于存储完整对象。 这样B在创建过程中进行属性注入时,先从一级缓存中获取A,获取失败则从二级缓存中获取。 这种获取bean的逻辑也可能出现其他线程获取到早期对象的问题,所以还是要加互斥锁。只不过这里的加锁逻辑可以下沉到二级缓存。那么普通的getBean请求可以直接从一级缓存获取对象,而不用去竞争锁。
(3)二级缓存如何解决AOP问题?
代理对象提前创建。
Spring支持以CGLIB和JDK动态代理的方式为对象创建代理类以提供AOP支持,代理对象的创建通常是在bean初始化完成之后进行(通过BeanPostProcessor后置处理器)。 如果没有循环依赖,那么代理对象依然在初始化完成后创建,如果有循环依赖,那么提前创建代理对象。 如何判断发生了循环依赖? 在B创建的过程中获取A的时候,发现二级缓存中有A,就说明发生了循环依赖,此时就为A创建代理对象,将其覆盖到二级缓存中,并且将代理对象复制给B的对应数字。 当出现多级循环依赖的时候,可以在对象实例化完成之后,将beanName存在一个Set中,标识对应的bean正在创建中,而当其他对象创建的过程依赖某个对象的时候,判断其是否在这个Set中,如果在就说明发生了循环依赖。
(4)那么三级缓存有什么作用?
虽然仅仅依靠二级缓存能够解决循环依赖和AOP的问题,但是从解决方案上来看,维护代理对象的逻辑和getBean的逻辑过于耦合。
三级缓存的key还是为beanName,但是value是一个函数(ObjectFactory#getBean方法),在该函数中执行获取早期对象的逻辑:getEarlyBeanReference方法。 在getEarlyBeanReference方法中,Spring会调用所有SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法,通过该方法可以修改早期对象的属性或者替换早期对象。这个是Spring留给开发者的另一个扩展点,虽然我们很少使用,不过在循环依赖遇到AOP的时候,代理对象就是通过这个后置处理器创建。
参考资料
[1] 🕊️ 鸟叔博客:https://mrbird.cc/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Spring%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%96.html
[2] 💤 黑哥知乎:https://zhuanlan.zhihu.com/p/375308988