1.概述
之前我们对Spring Bean
生命周期和Bean实例化、属性填充、初始化、销毁等整体流程进行全面分析与总结,不熟悉的可查看:Spring Bean生命周期。我们也提到在创建Bean
过程中贯穿着循环依赖问题,Spring使用三级缓存解决循环依赖,这也是一个重要的知识点,所以我们下面就来看看Spring是如何使用三级缓存解决循环依赖的。
什么是循环依赖?
循环依赖,也可以叫做循环引用,就是一个或者多个bean对象之间互相引用,存在依赖关系,大致相互引用情况如下:
由上可知,循环依赖其实就是一个闭环,像图中情况二Spring在创建单例bean A的时候发现引用了B,这时候就会去容器中查找单例bean B,发现没有然后就会创建bean B,创建bean B时又发现引用了bean A,这时候又会去容器中查找bean A,发现没有,接下来就会循环重复上面的步骤,这是不是像极了死锁?其实循环依赖就是一个死循环的过程。
项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用
Github地址:https://github.com/plasticene/plasticene-boot-starter-parent
Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent
微信公众号:Shepherd进阶笔记
2.循环依赖案例
在讲述循环依赖示例前,我们先来看看什么是依赖注入?知道了依赖注入之后才能了解循环依赖出现的场景所在。
2.1 什么是依赖注入
依赖注入是Spring IOC(控制反转)模块的一个核心概念,DI (Dependency Injection):依赖注入是指在 Spring IOC 容器创建对象的过程中,将所依赖的对象通过配置进行注入,我们可以通过依赖注入的方式来降低对象间的耦合度。这里的配置注入可以基于XML配置文件也可以基于注解配置,当下注解配置开发是主流,所以在这里主要讨论基于注解的注入方式,基于注解的常规注入方式通常有三种:
- 基于field属性注入
- 基于setter方法注入
- 基于构造器注入
接下来就让我们分别来看看这三种常规的注入方式。
field属性注入
这种方式是我们平时开发中使用最多的,原因是这种方式使用起来非常简单,代码更加简洁。
@Service
public class UserService {
@Autowired
private UserDAO userDAO; //通过属性注入
}
setter方法注入
@Service
public class UserService {
private UserDAO userDAO;
@Autowired //通过setter方法实现注入
public void setUserDAO(serDAO userDAO) {
this.userDAO = userDAO;
}
}
构造器注入
@Service
public class UserService {
private UserDAO userDAO;
@Autowired //通过构造器注入
public UserService(UserDAO userDAO) {
this.userDAO = userDAO;
}
}
2.2 循环依赖示例
首先需要强调一点,虽然Spring允许Bean对象的循环依赖,但事实上,项目中存在Bean的循环依赖,是Bean对象职责划分不明确、代码质量不高的表现,如果存在大量的Bean之间循环依赖,那么代码的整体设计也就越来越糟糕。所以SpringBoot在后续的版本中终于受不了这种滥用,默认把循环依赖给禁用了!从2.6版本开始,如果你的项目里还存在循环依赖,SpringBoot将拒绝启动!
我在2.7版本的Spring Boot中有两个Bean:UserService, RoleService
,UserService
需要查询某个用户有哪些角色,RoleService
需要查询某个角色关联了哪些用户,这样就形成了相互引用循环依赖啦,代码如下:
@Service
public class UserService {
@Autowired
private RoleService roleService;
}
@Service
public class RoleService {
@Autowired
private UserService userService;
}
启动项目报错如下:存在循环依赖
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| UserService (field private com.plasticene.fast.service.impl.RoleService com.plasticene.fast.service.impl.UserService.roleService)
↑ ↓
| RoleService (field private com.plasticene.fast.service.impl.UserService com.plasticene.fast.service.impl.RoleService.userService)
└─────┘
接下来我们在配置文件中配置开启允许循环依赖
spring:
main:
allow-circular-references: true
项目就能正常启动了。
上面演示的基于field数据注入
方式的循环依赖,在开启允许循环依赖的配置的情况下项目正常启动,接下来我们基于开启配置的情况改为构造器依赖注入
看看:
@Service
public class UserService {
private RoleService roleService;
@Autowired
public UserService(RoleService roleService) {
this.roleService = roleService;
}
}
@Service
public class RoleService {
private UserService userService;
@Autowired
public RoleService(UserService userService) {
this.userService = userService;
}
}
在开启允许循环依赖的配置启动项目还是会报错。是的,对于构造器的循环依赖,Spring 是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖
3.循环依赖解决方案
Spring
解决循环依赖
的核心思想在于提前曝光
,使用三级缓存进行提前曝光。
在DefaultListableBeanFactory
的上四级父类DefaultSingletonBeanRegistry
中提供如下三个Map作为三级缓存:
public class DefaultSingletonBeanRegistry ... {
//1、最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean,称之为"一级缓存"
Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
//2、早期Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用了,称之为"二级缓存"
Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
//3、单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean,称之为"三级缓存"
Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
}
我们知道在项目启动时会进行Bean的加载、注入到Spring容器中,当创建某个Bean时发现引用依赖于另一个Bean,就会进行依赖查找,就会来到顶层接口BeanFactory的#getBean()方法
,所以接下来看看AbstractBeanFactory的#doGetBean()
的实现逻辑,发现首先会根据 beanName
从单例 bean 缓存中获取,如果不为空则直接返回
Object sharedInstance = getSingleton(beanName);
这个#getSingleton()
是在DefaultSingletonBeanRegistry
实现的:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
// 从单例缓存中加载bean
Object singletonObject = this.singletonObjects.get(beanName);
// 单例缓存中没有获取到bean,同时bean在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 从 earlySingletonObjects 获取
singletonObject = this.earlySingletonObjects.get(beanName);
// 还是没有加载到bean,并且允许提前创建
if (singletonObject == null && allowEarlyReference) {
// 对单例缓存map加锁
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
// 再次从单例缓存中加载bean
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 再次从 earlySingletonObjects 获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 从 singletonFactories 中获取对应的 ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 获得 bean
singletonObject = singletonFactory.getObject();
// 添加 bean 到 earlySingletonObjects 中
this.earlySingletonObjects.put(beanName, singletonObject);
// 从 singletonFactories 中移除对应的 ObjectFactory
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
这个方法就是从三级缓存中获取Bean对象,可以看到这里先从一级缓存singletonObjects
中查找,没有找到的话接着从二级缓存earlySingletonObjects
,还是没找到的话最终会去三级缓存singletonFactories
中查找,需要注意的是如果在三级缓存中找到,
就会从三级缓存升级到二级缓存了。所以,二级缓存存在的意义,就是缓存三级缓存中的 ObjectFactory 的 #getObject()
方法的执行结果,提早曝光的单例 Bean 对象。
#getSingleton()
返回空就会接着执行AbstractBeanFactory的#doGetBean()
的下面逻辑,来到:
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
可以看到原型模式的Bean循环依赖是直接报错,对于单例模式的Bean循环依赖Spring通过三级缓存提前曝光Bean来解决,因为单例Bean在整个容器中就一个,但是原型模式是每次都会创建一个新的Bean,无法使用缓存解决,所以直接报错了。
经过一系列代码之后还是没有当前查找的Bean,就会创建一个Bean,来到代码:
// 上面的缓存中没找到,需要根据不同的模式创建
// bean实例化
// Create bean instance.
if (mbd.isSingleton()) { // 单例模式
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
// 显式从单例缓存中删除 Bean 实例
// 因为单例模式下为了解决循环依赖,可能他已经存在了,所以销毁它
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
最终来到了AbstractAutowireCapableBeanFactory的#createBean()
,真正执行逻辑实现的是#doCreateBean()
方法的里面代码片段如下所示:
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
// <4> 解决单例模式的循环依赖
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 提前将创建的 bean 实例加入到 singletonFactories 中
// 这里是为了后期避免循环依赖
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
这里将创建的Bean工厂对象加入到 singletonFactories 三级缓存中,用来生成半成品的Bean
并放入到二级缓存中,提前曝光bean意味着别的bean引用它时依赖查找就可以在前面的#getSingleton()
中拿到当前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)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
可以看出,singletonFactories
这个三级缓存是解决 Spring Bean 循环依赖的重要所在。同时这段代码发生在 #createBeanInstance(...)
方法之后,也就是说这个 bean 其实已经被创建出来了,但是它还不是很完美(没有进行属性填充和初始化),但是对于其他依赖它的对象而言已经足够了(可以根据对象引用定位到堆中对象),能够被认出来了。所以 Spring 在这个时候将该对象提前曝光出来,可以被其他对象所使用。
当然也需要注意到addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))
的() -> getEarlyBeanReference(beanName, mbd, bean)
匿名函数调用,使用lambda方式生成一个ObjectFactory
对象放到三级缓存中,提前曝光的是ObjectFactory
对象,在被注入时才在ObjectFactory.getObject
方式内实时生成代理对象,也就是调用#getEarlyBeanReference()
进行实现的。
// 这里如果当前Bean需要aop代理增强,就是这里生成代理Bean对象的
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
这也是为什么 Spring 需要额外增加 singletonFactories
三级缓存的原因,解决 Spring 循环依赖情况下的 Bean 存在动态代理等情况,不然循环注入到别人的 Bean 就是原始的,而不是经过动态代理的!
这里有个值得思考的问题:为什么要包装一层ObjectFactory对象存入三级缓存,说是为了解决Bean对象存在aop代理情况,那么直接生成代理对象半成品Bean放入二级缓存中,这样就可以不用三级缓存了!!!这么一说使用三级缓存的意义在哪里
首先需要明确一点:正常情况下(没有循环依赖),Spring
都是在创建好完成品Bean
之后才创建对应的代理对象
。为了处理循环依赖,Spring
有两种选择:
- 不管有没有
循环依赖
,都提前
创建好代理对象
,并将代理对象
放入缓存,出现循环依赖
时,其他对象直接就可以取到代理对象并注入。 - 不提前创建好代理对象,在出现
循环依赖
被其他对象注入时,才实时生成代理对象
。这样在没有循环依赖
的情况下,Bean
就可以按着Spring设计原则
的步骤来创建。
显然Spring
使用了三级缓存,选择第二种方案,这是为啥呢?
原因是:如果要使用二级缓存
解决循环依赖
,意味着Bean在构造
完后就创建代理对象
,这样违背了Spring设计原则
。Spring结合AOP跟Bean的生命周期,是在Bean创建完全
之后通过AnnotationAwareAspectJAutoProxyCreator
这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization
方法中对初始化后的Bean完成AOP代理。如果出现了循环依赖
,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。
经过实例化,初始化、属性赋值等操作之后,bean对象已经是一个完整的实例了,最终会调用DefaultSingletonBeanRegistry的#addSingleton()
将完整bean放入一级缓存singletonObjects
。
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);
}
}
到这里一个真真正正完整的Bean已经存入Spring
容器中,可以随意被使用啦。
这里还涉及到一个比较细节的知识点,也是面试的一个考点:说说BeanFactory、FactoryBean及ObjectFactory三者的作用和区别?
BeanFactory
: BeanFactory是IOC容器的核心接口,用于管理Bean的一个工厂接口类,主要功能有实例化、定位、配置应用程序中的对象及建立这些对象间的依赖
FactoryBean
: 一般情况下,Spring 通过反射机制利用 bean 的 class 属性指定实现类来实例化 bean 。某些情况下,实例化 bean 过程比较复杂,如果按照传统的方式,则需要提供大量的配置信息,配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring 为此提供了一个 FactoryBean 的工厂类接口,用户可以通过实现该接口定制实例化 bean 的逻辑。FactoryBean在BeanFacotry的实现中有着特殊的处理,如果一个对象实现了FactoryBean 那么通过它get出来的对象实际是
factoryBean.getObject() 得到的对象,如果想得到FactoryBean必须通过在 ‘&’ + beanName 的方式获取。
ObjectFactory
: ObjectFactory则只是一个普通的对象工厂接口,从上面可以看到spring对ObjectFactory的应用之一就是,将创建对象 的步骤封装到ObjectFactory中,从而通过ObjectFactory在合适的时机创建合适的bean
4.总结
以上全部就是Spring
对单例Bean的循环依赖的解决方案,核心就是使用三级缓存提前曝光Bean对象。两个Bean A,B互相引用循环依赖,Spring
的解决过程如下:
- 通过构建函数创建A对象(A对象是半成品,还没注入属性和调用init方法)。
- A对象需要注入B对象,发现缓存里还没有B对象,将
半成品对象A
放入半成品缓存
。 - 通过构建函数创建B对象(B对象是半成品,还没注入属性和调用init方法)。
- B对象需要注入A对象,从
半成品缓存
里取到半成品对象A
。 - B对象继续注入其他属性和初始化,之后将
完成品B对象
放入完成品缓存
。 - A对象继续注入属性,从
完成品缓存
中取到完成品B对象
并注入。 - A对象继续注入其他属性和初始化,之后将
完成品A对象
放入完成品缓存
。
最后附上一张源码执行流程图:(可自行放大查看)