Spring 解决循环依赖和对三级缓存的思考

一、首先,看一下几种循环依赖

1、 构造器注入循环依赖

@Service
public class A {
    public A(B b) {
    }
}
@Service
public class B {
    public B(A a) {
    }
}

2 、singleton模式field属性 or setter注入循环依赖

@Service
public class A {
    @Autowired
    private B b;
}

@Service
public class B {
    @Autowired
    private A a;
}

3、 prototype模式field属性注入循环依赖

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class A {
    @Autowired
    private B b;
}

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class B {
    @Autowired
    private A a;
}

不能解决的情况:

  • 构造器注入循环依赖

  • prototype模式field属性注入循环依赖

能解决的情况:

  • singleton模式field属性注入(setter方法注入)循环依赖

二、SpringBoot如何循环解决的呢?

就是通过三级缓存

//一级缓存,完整的单例对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
//三级缓存,bean的ObjectFactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
//二级缓存,不完整的对象
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);

首先说说Bean的生命周期

  • 1、当我们Spring容器初始化完成后,就会通过ResourceLoader,解析注解等把Bean加载到容器中,生成BeanDifinition对象,然后通过BeanWrapper进行实例化。
  • 2、populateBean()设置对象属性(执行aware接口啦,BeanBeforPostProcessor)
  • 3、最后初始化IntializingBean,innit-method(BeanAfterPostProcessor),完成AOP代理

发生循环依赖的的地方就是在设置属性的地方populateBean(),详细过程如下

  • 1、A在实例化之后,三级缓存addSingletonFactoris(beanName,lamda)这一步是放入的bean的ObejctFactory,然后去populate属性设置B。
  • 2、B进行实例化,之后B的ObjectFactory也是放入三级缓存,然后去populate属性,发现B依赖A的,于是去一二三级缓存中找A,在三级缓存中找到了A的ObejctFactory,如果此时的A是aop那么返回的是一个proxybean,也就是提前让A的aop暴露,否则就返回自身,所以B中的属性A有两种情况A有AOP A实际上是一个代理类,否则就是A的实例,然后删除A三级缓存,放入二级缓存,然后B就初始化完成,放入一级缓存。
  • 3、在返回A的生命周期来,从一级缓存获取B,属性设置完成,完成初始化。

Spring AOP循环依赖

三、关于为什么三级缓存?

添加三级缓存addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));具体方法如下。它实际上就是调用了后置处理器的getEarlyBeanReference,而真正实现了这个方法的后置处理器只有一个,就是通过@EnableAspectJAutoProxy注解导入的AnnotationAwareAspectJAutoProxyCreator。如果没有AOP那么直接返回原对象了,所以三级缓存肯定是用于AOP增强的。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
            Iterator var5 = this.getBeanPostProcessors().iterator();

            while(var5.hasNext()) {
                BeanPostProcessor bp = (BeanPostProcessor)var5.next();
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor)bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }

        return exposedObject;
    }

四、在给B注入的时候为什么要注入一个代理对象?

如果我们对A进行了AOP代理的话,那么此时getEarlyBeanReference将返回一个代理后的对象,而不是实例化阶段创建的对象,这样就意味着B中注入的A将是一个代理对象而不是A的实例化阶段创建后的对象。这样的话,也不违背Spring bean生成流程,实例化-赋值-初始化-增强(AOP)。

五、明明初始化的时候是A对象,那么Spring是在哪里将代理对象放入到容器中的呢?

Object earlySingletonReference = this.getSingleton(beanName, false);
            if (earlySingletonReference != null) {
                if (exposedObject == bean) {
                    exposedObject = earlySingletonReference;

在完成初始化后,Spring又调用了一次getSingleton方法,这一次传入的参数又不一样了,false可以理解为禁用三级缓存,前面图中已经提到过了,在为B中注入A时已经将三级缓存中的工厂取出,并从工厂中获取到了一个对象放入到了二级缓存中,所以这里的这个getSingleton方法做的时间就是从二级缓存中获取到这个代理后的A对象。exposedObject == bean可以认为是必定成立的,除非你非要在初始化阶段的后置处理器中替换掉正常流程中的Bean,例如增加一个后置处理器。

六、一些思考

之前看了很多文章都没有把为啥一定要使用三级缓存的说得很清楚,我说下我自己的思考假设我们在这里直接使用二级缓存的话,addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))那么意味着所有的Bean在这一步都要完成AOP代理,违背了Spring在结合AOP跟Bean的生命周期的设计,Spring结合AOP跟Bean的生命周期本身就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。如果出现了循环依赖,先给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

所以二级缓存模式是可以的,但是我觉得Spring没那么做还是考虑,规范性的问题。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值