当前位置: 首页 > 科技观察

为什么Spring需要三级缓存来解决循环依赖,而不是二级缓存

时间:2023-03-16 01:10:51 科技观察

前言在使用spring框架的日常开发中,bean之间的循环依赖太频繁了。Spring帮我们解决了循环依赖问题。对于我们开发者来说是潜移默化的。下面详细分析下spring是如何解决bean之间的循环依赖的。为什么使用三级缓存而不是二级缓存bean生命周期。首先,你需要了解bean在spring中的生活。Cycle,bean在spring中的加载过程,可以更清楚的知道spring是如何解决循环依赖bean的生命周期方法的。我们在spring的BeanFactory工厂中列出了很多接口,代表了bean的生命周期。我们主要记住的是我用红圈圈出来的接口,结合spring的源码看看这些接口主要调用在哪里。AbstractAutowireCapableBeanFactory类的doCreateBean方法是创建bean的开始。我们可以看到首先需要实例化bean,也就是在堆中为这个对象开辟一块内存空间。createBeanInstance方法中的逻辑大概是利用反射生成实例对象。这意味着该对象还没有填充属性,即@Autowired注解的属性还没有注入到createBeanInstance方法中。我们可以看到第二步是填充bean的成员属性,populateBean方法中的逻辑大致就是注入使用注入属性的注解。如果在注入过程中发现注入的对象还没有生成,就会去生产。对于注入的对象,第三步是调用initializeBean方法初始化bean,即调用上面提到的接口initializeBean方法初始化bean。可以看到在initializeBean方法中,首先调用了Aware接口的方法。下面我们详细看一下invokeAwareMethods方法会调用Aware接口的那些方法。invokeAwareMethods方法我们可以知道,如果我们实现BeanNameAware、BeanClassLoaderAware、BeanFactoryAware这三个Aware接口,就会依次调用setBeanName()、setBeanClassLoader()、setBeanFactory()方法,然后查看applyBeanPostProcessorsBeforeInitialization()源码applyBeanPostProcessorsBeforeInitialization方法,发现如果实例化类实现了BeanPostProcessor接口,就会执行postProcessBeforeInitialization方法。这里需要注意的是,如果多个类实现了BeanPostProcessor接口,那么多个实现类都会执行postProcessBeforeInitialization方法。可以看出for循环是顺序执行的。还有一点需要注意的是,如果A类加载到spring容器中,A类也会重写BeanPostProcessor接口的postProcessBeforeInitialization方法。这时候要注意A类的postProcessBeforeInitialization方法是不会Get执行的,因为A类还没有加载,还没有完全放入spring的singletonObjects一级缓存再看一个注意点,ApplicationContextAwareProcessor接口的invokeAwareInterfaces方法,可以看到ApplicationContextAwareProcessor也实现了BeanPostProcessor接口,重写了postProcessBeforeInitialization方法,在方法中调用了invokeAwareInterfaces方法,invokeAwareInterfaces方法也说了如果很多意识到接口被实现,它会依次执行相应的方法。值得注意的是ApplicationContextAware接口的setApplicationContext方法。再看invokeInitMethods的源码,发现如果实现了InitializingBean接口,重写afterPropertiesSet方法,就会调用afterPropertiesSet方法,最后是否指定init-method,可以通过指定标记,或@Bean注释的initMethod。最后查看applyBeanPostProcessorsAfterInitialization源码图,发现applyBeanPostProcessorsAfterInitialization源码与之前的postProcessBeforeInitialization方法类似。postProcessAfterInitialization方法。整个bean的生命执行流程如上图所示,在什么地方调用了哪个接口方法,方法的执行流程最后做一个bean生命流程的流程图总结。bean的三级缓存解决循环依赖一节对bean的生命周期进行了整体流程分析,对于spring如何解决循环依赖有很大的帮助。之前我们分析填充属性的时候,如果发现spring中没有生成属性,就会运行生成属性对象实例。我们可以看到,在填充属性的时候,spring会通过ObjectFactory半成品提前暴露出实例化的bean。之所以叫半成品,是因为此时实例化了bean对象,但是没有填充属性。它是bean实例对象的一个??不完整的三级缓存。Spring使用singletonObjects、earlySingletonObjects、singletonFactories三级缓存来解决。也就是说,在使用三个Map三级缓存时,可以看到三级缓存中存储的对象。这里重点关注二级缓存earlySingletonObjects和三级缓存singletonFactory,一级缓存可以忽略。前面我们说过,第一个实例化的bean会通过ObjectFactory半成品提前暴露在三级缓存中。singletonFactory是传入的匿名内部类,调用ObjectFactory.getObject()最终会调用getEarlyBeanReference方法。下面看看循环依赖中如何获取其他半成品的实例对象。我们假设有这样一个场景,AService依赖BService,BService又依赖AService。首先实例化AService,实例化通过ObjectFactory半成品暴露给三级缓存填写属性BService。如果BService还没有加载,会先加载BService,再加载BService。在实例化的过程中,填充属性AService时,也会通过ObjectFactory半成品暴露给三级缓存。这时候就可以从三级缓存中获取半成品ObjectFactory了。获取到ObjectFactory对象后,调用ObjectFactory.getObject()方法最终会调用getEarlyBeanReference()方法。getEarlyBeanReference方法的主要逻辑大致描述了如果bean被AOP切面代理,则返回beanProxy对象。如果未代理,则返回原始bean实例。这个时候我们会发现可以拿到bean实例(属性没有填充),然后从三级缓存中取出,放到二级缓存中的earlySingletonObjects,这时候B注入了一个semi-完成了实例A的对象,但是B的初始化完成后,A会继续进行后续的初始化操作,最后B会注入一个完整的A实例,因为它们在内存中是同一个对象。以下是重点。我们发现二级缓存似乎有点多余。好像可以去掉。只有一级和三级缓存才能解决循环依赖问题???只要两个缓存确实可以解决循环依赖的问题,但是有个前提,bean没有被AOP代理。如果bean是AOP代理的,那么只有两个缓存是解决不了问题的。我们看下bean被AOP代理的场景,发现AService的testAopProxy被AOP代理了,查看传入的匿名内部类的getEarlyBeanReference返回的对象,发现返回的是singletonFactory.getObject()AService的代理对象,仍然由CGLIB代理。再看一个,再执行singletonFactory.getObject(),看返回的是不是同一个AService的代理对象。我们会发现再次执行singleFactory.getObject()方法是一个新的代理对象,这样就会出问题,因为AService是单例的,每次执行singleFactory.getObject()方法都会生成一个新的代理对象。假设只有一级和三级缓存,我每次都从三级缓存中获取singleFactory对象,执行getObject()方法会生成一个新的代理对象,这是不行的,因为AService是一个单例,所以这里我们需要使用二级缓存来解决这个问题,将执行singleFactory.getObject()生成的对象放到二级缓存中去,再去二级缓存中去得到它。无需再次执行singletonFactory.getObject()方法生成新的代理对象,保证代理对象始终只有一个(该对象可能依赖多个其他对象)。还有一点需要注意,既然singleFactory.getObject()返回的是代理对象,那么注入的也应该是代理对象。我们可以看到注入的确实是CGLIB代理的AService对象。所以如果没有AOP,二级缓存确实可以解决循环依赖的问题。如果加上AOP,二级缓存无法解决。不可能每次执行singleFactory.getObject()方法都为我生成一个新的代理对象,所以需要另外一个缓存来保存生成的代理对象