开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 16 天,点击查看活动详情
大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实,最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈
前言
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的设计原则
。
大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实,最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 16 天,点击查看活动详情