前言
Spring 的循环依赖,也就是两个 bean 之间产生了互相依赖,那么引出的问题就是如何顺利的将两个 bean 创建出来并注册到容器中。更有甚者,产生了循环依赖的 bean 还需要生成动态代理对象,这种情况则比普通的循环依赖更为复杂。
本篇文章将对 Spring 中的循环依赖进行详细分析,结合示例工程,流程图示和源码,力求一文阐释清楚 Spring 中的循环依赖问题以及如何解决,并会在最后给出 Spring 中的三个缓存的具体作用。
在开始本文的分析前,有如下几点概念说明。
- bean 的实例化,就是将 bean 的对象 new 出来,称为 bean 的原始对象,原始对象没有完成属性注入,不能称为 bean;
- bean 的属性注入,就是为 bean 的原始对象注入其它 bean 即依赖注入,完成依赖注入的原始对象,此时可以作为 bean 放入容器;
- bean 的初始化可以理解为:bean 实例化 + bean 属性注入。
Spring 版本:5.3.2
正文
一. 循环依赖的产生
如果有两个业务类实现如下。
@Service
public class MyServiceA {
@Autowired
private MyServiceB myServiceB;
}
@Service
public class MyServiceB {
@Autowired
private MyServiceA myServiceA;
}
复制代码
那么 Spring 在初始化 MyServiceA 的 bean 时候,会为 MyServiceA 的原始对象注入 MyServiceB 的 bean,此时由于容器中没有 MyServiceB 的 bean,所以 Spring 又会去初始化 MyServiceB 的 bean,初始化 MyServiceB 的 bean 的时候,会为 MyServiceB 的原始对象注入 MyServiceA 的 bean,此时就发生了循环依赖。
后续都将 MyServiceA 简称为 A,将 MyServiceB 简称为 B。
二. 循环依赖的解决
如下是循环依赖中最复杂的一种情况,即两个需要生成动态代理的 bean 之间形成了循环依赖。
业务类如下所示。
@Service
public class MyServiceA implements InitializingBean {
private String initMessage;
@Autowired
private MyServiceB myServiceB;
@MyAnnotation
public void executeA() {
System.out.println("MyService A execute.");
}
@Override
public void afterPropertiesSet() {
initMessage = "MyService A.";
}
}
@Service
public class MyServiceB implements InitializingBean {
private String initMessage;
@Autowired
private MyServiceA myServiceA;
@MyAnnotation
public void executeB() {
System.out.println("MyService B execute.");
}
@Override
public void afterPropertiesSet() {
initMessage = "MyService B.";
}
}
复制代码
在业务类中使用了 @MyAnnotation 注解来修饰方法,该注解是自定义注解,没有任何含义,仅为了帮助在 SpringAOP 中进行切点声明,@MyAnnotation 注解定义如下。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
}
复制代码
切面如下所示。
@Aspect
@Component
public class MyAspect {
@Pointcut("@annotation(com.leanr.spring.ioc.mytest.MyAnnotation)")
private void allMethodPointcut() {}
@Before("allMethodPointcut()")
public void executeBeforeMethod(JoinPoint joinPoint) {
System.out.println("MyAspect execute.");
}
}
复制代码
配置类如下所示。
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(value = "com.leanr.spring.ioc.mytest")
public class MyConfig {}
复制代码
测试类如下所示。
public class MyTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext
= new AnnotationConfigApplicationContext(MyConfig.class);
}
}
复制代码
那么针对上面这种需要生成动态代理的 bean 之间存在循环依赖的情况,整个解决流程如下所示。
上述流程图中,出现了一级缓存,二级缓存和三级缓存,如果对这三个缓存没有概念,那么就暂时不要去深究,就当这三个缓存是三个 Map,在下面的章节,会结合源码,具体分析其作用。
三. 源码分析
在 Spring 中,如果基于 XML 配置 bean,那么使用的容器为 ClassPathXmlApplicationContext,如果是基于注解配置 bean,则使用的容器为 AnnotationConfigApplicationContext。以 AnnotationConfigApplicationContext 为例,其构造函数如下所示。
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
this();
register(componentClasses);
// 初始化容器
refresh();
}
复制代码
在 AnnotationConfigApplicationContext 的构造函数中会调用到 AbstractApplicationContext 的 refresh() 方法,实际上无论是基于 XML 配置 bean,还是基于注解配置 bean,亦或者是 Springboot 中,在初始化容器时都会调用到 AbstractApplicationContext 的 refresh() 方法中。下面看一下 refresh() 方法。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// ......
try {
// ......
// 初始化所有非延时加载的单例bean
finishBeanFactoryInitialization(beanFactory);
// ......
}
catch (BeansException ex) {
// ......
throw ex;
}
finally {
resetCommonCaches();
contextRefresh.end();
}
}
}
复制代码
重点关心 refresh() 方法中调用的 finishBeanFactoryInitialization() 方法,该方法会初始化所有非延时加载的单例 bean,其实现如下。
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
beanFactory.setTempClassLoader(null);
beanFactory.freezeConfiguration();
// 初始化所有非延时加载的单例bean
beanFactory.preInstantiateSingletons();
}
复制代码
在 finishBeanFactoryInitialization() 方法中会调用到 DefaultListableBeanFactory 的 preInstantiateSingletons() 方法,如下所示。
public void preInstantiateSingletons() throws BeansException {
// ......
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// 在这个循环中通过getBean()方法初始化bean
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 判断是否是FactoryBean
if (isFactoryBean(beanName)) {
// ......
}
else {
// 不是FactoryBean,则通过getBean()方法来初始化bean
getBean(beanName);
}
}
}
// ......
}
复制代码
那么这里需要注意,Spring 中初始化 bean,是通过调用容器的 getBean() 方法来完成,在 getBean() 方法中如果获取不到 bean,此时就会初始化这个 bean,AbstractBeanFactory 的 getBean() 方法的实现如下。
public Object getBean(String name) throws BeansException {
// 有三种情况会调用到这里
// 1. 容器启动的时候初始化A,所以调用到这里以进行A的初始化
// 2. 初始化A的时候要属性注入B,所以调用到这里以进行B的初始化
// 3. 初始化B的时候要属性注入A,所以调用到这里以获取A的bean
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 beanInstance;
// 情况1和情况2:去一级缓存中获取bean,是获取不到的
// 情况3:依次去一级缓存,二级缓存和三级缓存中获取A的bean
// 情况3:在本示例中,最终会在三级缓存中获取到A原始对象对应的ObjectFactory
// 情况3:然后通过A原始对象对应的ObjectFactory获取A原始对象(的代理对象)
// 情况3:获取到A原始对象(的代理对象)后,会将其放入二级缓存
// 情况3:然后将A原始对象对应的ObjectFactory从三级缓存删除
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// ......
}
else {
// 非单例bean是无法支持循环依赖的,所以这里判断是否是非单例bean的循环依赖场景,如果是则抛出异常
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// ......
try {
// ......
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// ......
// 情况1和情况2都会执行到这里
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
// 在上面的getSingleton()方法中会调用到createBean()方法
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// ......
}
catch (BeansException ex) {
// ......
}
finally {
beanCreation.end();
}
}
return adaptBeanInstance(name, beanInstance, requiredType);
}
复制代码
实际会有三种情况调用到 AbstractBeanFactory 的 getBean() 方法,总结如下。
- 容器初始化的时候,初始化 A,这种情况,是无法从一级缓存中获取到 A 的 bean(或者代理 bean)的,所以调用 getSingleton(String) 方法会返回 null,然后调用 getSingleton(String, ObjectFactory<?>) 方法来获取(初始化)A;
- 初始化 A 的时候,会属性注入 B,此时会调用到 AbstractBeanFactory 的 getBean() 方法来初始化 B,这种情况的逻辑同上;
- 初始化 B 的时候,会属性注入 A,此时会调用到 AbstractBeanFactory 的 getBean() 方法来获取 A 的 bean(或者代理 bean),并且能够在 getSingleton(String) 方法中获取到 A 的原始对象对应的 ObjectFactory,然后通过 A 的原始对象对应的 ObjectFactory 的 getObject() 方法获取到 A 的原始对象(的代理对象),并将其放入二级缓存,最后将 A 的原始对象对应的 ObjectFactory 从三级缓存中删除。
上述的情况 1 和情况 2,在 getSingleton(String) 方法中只会去一级缓存获取,而情况三会依次去一级缓存,二级缓存和三级缓存中获取,这是因为有一个叫做 singletonsCurrentlyInCreation 的集合会对即将实例化并执行初始化逻辑的 bean 进行标记,那么在情况 1 和情况 2 中,singletonsCurrentlyInCreation 中都是没有 A 或 B 的标记的,只有情况 3 的 singletonsCurrentlyInCreation 中有 A 的标记,如果有标记,表明这时发生了循环依赖,所以需要去到二级缓存或者三级缓存中获取到提前暴露出来的对象。
如果是情况 1 或者情况 2,那么就会调用到 getSingleton(String, ObjectFactory<?>) 方法来初始化 A 或者 B,这里传入的 ObjectFactory<?> 实际是一个 Lambdas 表达式,所以调用 ObjectFactory 的 getObject() 方法,就会调用到 createBean() 方法。下面继续看 getSingleton(String, ObjectFactory<?>) 方法的逻辑。
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
// ......
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// ......
// 向singletonsCurrentlyInCreation集合中添加beanName
// 标记beanName对应的bean正在初始化,这里就是标记A或者B正在初始化
beforeSingletonCreation(beanName);
boolean newSingleton = false;
// ......
try {
// 调用getObject()方法,实际就是调用之前的createBean()方法
// 这里得到的singletonObject就是初始化后得到的bean(或者代理bean)
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// ......
}
catch (BeanCreationException ex) {
// ......
}
finally {
// ......
// 移除A或者B正在初始化的标记
afterSingletonCreation(beanName);
}
if (newSingleton) {
// 将A或者B的bean放入一级缓存
// 删除A或者B在二级缓存中的原始对象(的代理对象)
// 删除A或者B在三级缓存中的ObjectFactory
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
复制代码
DefaultSingletonBeanRegistry 的 getSingleton(String, ObjectFactory<?>) 方法中首先会标记 A 或者 B 正在初始化,然后调用到 AbstractAutowireCapableBeanFactory 的 createBean() 方法,在 createBean() 方法中会真正的把对象 new 出来以得到原始对象,然后为原始对象属性注入其它 bean(循环依赖就是在这里发生)和执行初始化逻辑,在 createBean() 方法执行完后,就会得到真正可用的 bean(或代理 bean),之后就从 singletonsCurrentlyInCreation 中移除正在初始化的标记,然后将 bean(或者代理 bean)放入一级缓存,然后删除在二级缓存中的原始对象(的代理对象),删除在三级缓存中的 ObjectFactory。那么重点就是 createBean() 方法,其实现如下。
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// 拿到BeanDefinition
RootBeanDefinition mbdToUse = mbd;
// ......
try {
// 初始化在这里
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
// ......
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
}
}
复制代码
继续跟进 doCreateBean() 方法,如下所示。
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
BeanWrapper instanceWrapper = null;
// ......
if (instanceWrapper == null) {
// 把A或者B的对象new出来,称作原始对象
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 这里的bean就是A或者B的原始对象,此时没有被属性注入,也没有执行初始化逻辑
Object bean = instanceWrapper.getWrappedInstance();
// ......
// 这里计算结果为true,目的是提前将A或B的原始对象对应的ObjectFactory放到三级缓存中
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// ......
// 将A或者B的原始对象对应的ObjectFactory放到三级缓存中
// 那么ObjectFactory的getObejct()方法实际就会调用到getEarlyBeanReference()方法
// 如果需要动态代理,getEarlyBeanReference()方法会返回原始对象的代理对象
// 如果不需要动态代理,getEarlyBeanReference()方法会返回原始对象
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
Object exposedObject = bean;
try {
// 这里为A或者B的原始对象进行属性注入
populateBean(beanName, mbd, instanceWrapper);
// 调用initializeBean()方法来为A或者B的原始对象执行初始化的逻辑
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
// ......
}
if (earlySingletonExposure) {
// 这里会从二级缓存中将A或者B的原始对象(的代理对象)获取出来
// 如果是初始化A的时候调用到这里,那么能够获取出来A的原始对象(的代理对象)
// 如果是初始化B的时候调用到这里,那么不能够获取出来B的原始对象(的代理对象)
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
// 只有A能进到这里
// 将A的原始对象(的代理对象)替换A的原始对象
exposedObject = earlySingletonReference;
}
// ......
}
}
// ......
// 将原始对象(的代理对象)返回
// 其实这里的对象已经是可以使用的bean了
return exposedObject;
}
复制代码
doCreateBean() 方法可以概括如下。
- new 出对象以得到 A 或 B 的原始对象,然后将 A 或 B 的原始对象对应的 ObjectFactory 放入三级缓存(提前暴露原始对象的 ObjectFactory 到三级缓存中,以使得发生循环依赖的时候能够在三级缓存中通过原始对象的 ObjectFactory 获得原始对象或者原始对象的代理对象);
- 为原始对象进行属性注入,这里就分为两种情况。
- 为 A 原始对象属性注入 B 的 bean,那么就触发了初始化 B 的逻辑;
- 为 B 原始对象属性注入 A 的 bean,那么在这里,就会使用到 A 提前暴露到三级缓存中的 ObjectFactory 来获取 A 的原始对象(的代理对象),由前面的分析可知,通过 A 的 ObjectFactory 来获取到 A 的原始对象(的代理对象)后,会将其放入二级缓存,所以这个时候二级缓存中存在 A 的原始对象(的代理对象)。
- 调用 initializeBean() 方法来为 A 或者 B 的原始对象执行初始化的逻辑,initializeBean() 方法中有一个和循环依赖密切相关的执行步骤就是在后置处理器中为需要动态代理的对象生成代理对象,那么这里又有两种情况。
- 初始化 A 的时候执行到这里,说明 B 的初始化已经执行完毕了(因为 A 的属性注入已经结束了),所以 A 原始对象的代理对象就已经生成并且注入到了 B 的 bean(或者代理 bean)中,所以这里 A 就不能再在 initializeBean() 方法的后置处理器中再生成一个代理对象,如果生成就出现了两个代理对象违反了单例;
- 初始化 B 的时候执行到这里,B 原始对象的代理对象还没有在任何一个地方有生成,所以需要在 initializeBean() 方法的后置处理器中生成一个代理对象,并将这个代理对象返回。同时,在前面的分析中已知,只有通过调用 ObjectFactory 来获取原始对象(的代理对象)的时候,才会将原始对象(的代理对象)放入二级缓存,所以 B 的原始对象(的代理对象)是没有被放入到二级缓存中去的。
- 由于 doCreateBean() 方法是需要返回可用的 bean,所以在 A 和 B 都需要动态代理的情况下,还需要为属性注入和执行了初始化逻辑之后的对象再最后做一步操作,那就是将 A 和 B 的动态代理对象获取到并返回。
- 对于 A,A 的动态代理对象在二级缓存中,所以调用 getSingleton() 方法从二级缓存中获取并返回;
- 对于 B,B 的动态代理对象不存在于二级缓存中,但是当前 B 的对象已经是在后置处理器中生成的动态代理对象,所以直接返回。
那么到这里,Spring 使用三级缓存来解决循环依赖的问题就基本分析完毕,建议结合第二节中的流程图一起阅读。
四. 一级缓存作用
一级缓存的定义在 DefaultSingletonBeanRegistry 中,定义如下所示。
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
复制代码
一级缓存用于存放容器中可以使用的 bean 或者代理 bean,像例子中的 A 和 B,由于它们都需要生成动态代理对象,所以它们在一级缓存中存放的就是它们的代理 bean,后续容器中任何地方使用 A 和 B,都是使用的一级缓存中它们的代理 bean。
五. 二级缓存作用
二级缓存的定义在 DefaultSingletonBeanRegistry 中,定义如下所示。
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
复制代码
二级缓存用于存放原始对象(的代理对象),以让在有多重循环依赖的时候其它对象都从二级缓存中拿到同一个当前原始对象(的代理对象),并且只有在调用了三级缓存中的 ObjectFactory 的 getObject() 方法获取原始对象(的代理对象)时,才会将原始对象(的代理对象)放入二级缓存,而调用三级缓存中的 ObjectFactory 的 getObject() 方法获取原始对象(的代理对象)这种情况只会发生在有循环依赖的时候,所以,二级缓存在没有循环依赖的情况下不会被使用到
。
六. 三级缓存作用
三级缓存的定义在 DefaultSingletonBeanRegistry 中,定义如下所示。
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
复制代码
三级缓存用于存放原始对象对应的 ObjectFactory,每生成一个原始对象,都会将这个原始对象对应的 ObjectFactory 放到三级缓存中,通过调用 ObjectFactory 的 getObject() 方法,就能够在需要动态代理的情况下为原始对象生成代理对象并返回,否则返回原始对象,以此来处理循环依赖时还需要动态代理的情况。
为什么会存在三级缓存,主要原因就是:延迟代理对象的创建
。设想一下,如果在创建出一个原始对象的时候,就直接将这个原始对象的代理对象创建出来(如果需要创建的话),然后就放在二级缓存中,似乎感觉三级缓存就没有存在的必要了对吧,但是请打住,这里存在的问题就是,如果真这么做了,那么每一个对象在创建出原始对象后,就都会去创建代理对象,而 Spring 的原始设计中,代理对象的创建应该是由 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器的 postProcessAfterInitialization() 来完成,也就是:在对象初始化完毕后,再去创建代理对象
。如果真的只用两个缓存来解决循环依赖,那么就会打破 Spring 对 AOP 的一个设计思想。
总结
Spring 中发生循环依赖,简单讲就是 A 的 bean 依赖 B 的 bean,B 的 bean 又依赖 A 的 bean。
Spring 解决循环依赖的思路就是,当 A 的 bean 需要 B 的 bean 的时候,提前将 A 的 bean 放在缓存中(实际是将 A 的 ObjectFactory 放到三级缓存),然后再去创建 B 的 bean,但是 B 的 bean 也需要 A 的 bean,那么这个时候就去缓存中拿 A 的 bean,B 的 bean 创建完毕后,再回来继续创建 A 的 bean,最终完成循环依赖的解决。
那么有几个问题需要结合整篇文章的讨论,进行一个总结。
1. 为什么不直接使用一级缓存来解决循环依赖
一级缓存中预期存放的是一个正常完整的 bean,而如果只用一级缓存来解决循环依赖,那么一级缓存中会在某个时间段存在不完整的 bean,这是不安全的。
2. 为什么不直接使用一级缓存和二级缓存解决循环依赖
这个问题需要结合为什么引入三级缓存来分析。引用第六节的论述,使用一级缓存和二级缓存确实可以解决循环依赖,但是这要求每个原始对象创建出来后就立即生成动态代理对象(如果有的话),然后将这个动态代理对象放入二级缓存,这就打破了 Spring 对 AOP 的设计原则,即:在对象初始化完毕后,再去创建代理对象
。所以引入三级缓存,并且在三级缓存中存放一个对象的 ObjectFactory,目的就是:延迟代理对象的创建
,这里延迟到啥时候创建呢,有两种情况:第一种就是确实存在循环依赖,那么没办法,只能在需要的时候就创建出来代理对象然后放到二级缓存中,第二种就是不存在循环依赖,那就是正常的在初始化的后置处理器中创建。
因此不直接使用一级缓存和二级缓存来解决循环依赖的原因就是:希望在不存在循环依赖的情况下不破坏Spring对AOP的设计原则
。
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 16 天,点击查看活动详情