当前位置: 首页 > 后端技术 > Java

京东方面:为什么Spring需要三级缓存来解决循环依赖,而不是二级缓存?

时间:2023-04-01 21:21:15 Java

作者:斑粉,\来源:https://www.cnblogs.com/semi-...前言在日常使用spring框架的开发中,bean之间的循环依赖太频繁了,spring帮我们解决了解决循环依赖的问题,是我们开发者察觉不到的。下面详细分析下spring是如何解决bean之间的循环依赖的。为什么使用三级缓存而不是二级缓存bean生命周期。首先,你需要了解一下spring中bean的生命周期,以及spring中bean的加载过程,这样可以更清楚的知道spring是如何解决循环依赖的。我们在spring的BeanFactory工厂中列举了很多接口,代表了bean的生命周期。我们主要记住我用红圈圈出的接口是什么,结合spring的源码看看这些接口主要调用在哪里。AbstractAutowireCapableBeanFactory类的doCreateBean方法是创建bean的开始。我们可以看到首先需要实例化bean,也就是在堆中为这个对象开辟一块内存空间。createBeanInstance方法中的逻辑大概是利用反射生成实例对象。这意味着该对象还没有填充属性,也就是没有注入@Autowired注解的属性。我们可以看到第二步是填写bean的成员属性。populateBean方法中的逻辑大致就是注入使用注入属性的注解。如果在注入过程中发现注入的对象还没有生成,就会去production进行注入。对象,第三步是调用initializeBean方法初始化bean,也就是调用我们上面说的接口。可以看到在initializeBean方法中,首先调用了Aware接口的方法。我们看一下invokeAwareMethods方法会调用Aware接口的方法,我们可以知道,如果我们实现了BeanNameAware、BeanClassLoaderAware、BeanFactoryAware这三个Aware接口,就会调用setBeanName()、setBeanClassLoader()、依次setBeanFactory()方法,然后看applyBeanPostProcessorsBeforeInitializa如果有类实现了BeanPostProcessor接口,就会执行postProcessBeforeInitialization方法。这里需要注意的是,如果多个类实现了BeanPostProcessor接口,那么多个实现类都会执行postProcessBeforeInitialization方法。可以看到for循环是顺序执行的。是的,还有一点需要注意的是,如果A类加载到spring容器中,A类也会重写BeanPostProcessor接口的postProcessBeforeInitialization方法。这时候要注意,A类的postProcessBeforeInitialization方法是不会执行的,因为A类还没有执行。加载完成,还没有完全放入spring的singletonObjects一级缓存中。再看一个注意点。可以看到ApplicationContextAwareProcessor也实现了BeanPostProcessor接口,重写了postProcessBeforeInitialization方法。方法中调用了invokeAwareInterfaces方法,也写了invokeAwareInterfaces方法,如果实现了很多Aware接口,就会依次执行对应的方法。值得注意的是ApplicationContextAware接口的setApplicationContext方法。查看invokeInitMethods的源码,发现如果实现了InitializingBean接口,重写afterPropertiesSet方法,就会调用afterPropertiesSet方法,最后会调用是否指定init-method,可以通过标签或@Bean注解的initMethod。最后查看applyBeanPostProcessorsAfterInitialization源码图,发现和前面的postProcessBeforeInitialization方法类似,也是循环遍历实现BeanPostProcessor的接口实现类,执行postProcessAfterInitialization方法。整个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()方法对象都为我生成一个新的代理,所以我们需要使用另一个缓存来保存生成的代理对象。综上所述,我们首先讲了bean的加载过程。了解bean的加载过程,对于spring如何解决循环依赖问题很有帮助。后面我们会分析为什么spring需要用到三级缓存来解决循环依赖问题,而不是二级缓存。大家可以去网上试试AOP的情况,实践一下就明白为什么二级缓存不能解决AOP代理的场景了。在工作中,我一直认为编程代码不是最重要的,而是在工作中养成的编程思维。近期热点文章推荐:1.1000+Java面试题及答案(2022最新版)2.厉害了!Java协程来了。..3.SpringBoot2.x教程,太全面了!4.20w程序员红包封面,快拿。..5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!