如今,许多人仍然有关于循环依赖的争论。许多访调员也喜欢询问循环依赖性。即使在春季,他们也只要求周期依赖。在Chinafeatures中,许多人也谈论过。我认为,在春季框架中的许多出色设计中,这可以被视为有点污渍,这是为了妥协不良设计,您必须知道,整个框架中没有循环依赖性春季项目。这是因为春季项目太简单了,不能太简单吗?相反,春季比大多数项目都要复杂得多。相似,在Spring-boot 2.6.0 earease中,它还表明不再支持回收依赖性。如果支持它,则有必要手动打开(默认情况下是以前打开的),但是强烈建议通过修改项目来打破循环依赖性。
在本文中,我想分享我对循环依赖性的想法。当然,在此之前,我将为您带来有关循环依赖性的一些知识。
由于周期依赖性发生在注射依赖性过程中,因此让我们简要审查注射中的依赖性过程。
案件:
以上是一个非常简单的春季进入案例,注入了注射过程,并发生了注射过程。
该过程如下:
1.通过进入“ foo”来找到相应的beandefinition。如果您不知道BeanDefinition是什么,那么您可以将其理解为封装Bean.Antotation类信息的对象。
2.在豆类定义中使用BeanClass通过反射进行实例化,以获取我们的So alled Bean(Foo)。
3.分析BeanClass信息,获取确定注释的属性(bar)
4.使用属性名称(bar),再次致电,重复上述步骤
5.豆(bar)的价值值得foo的属性(bar)
以上是一个简单的过程描述
循环依赖性实际上是依赖的B,B也依赖于A,它构成了周期。从上面的示例中,如果bar也取决于foo,则会生成周期依赖性。
GetBean的过程可以说是递归功能。由于它是递归函数,因此有必要具有递归终止的条件。在GetBean中,很明显,这种终止条件是在填充属性的过程中返回。那么,如果现有过程似乎依赖于bar,bar将依靠foo,会发生什么?
1.创建foo对象
2.填充属性时,我发现foo对象取决于bar
3.创建栏对象
4.填充属性时,我发现条对象取决于foo
5.创建foo对象
6.填充属性时,我发现foo对象取决于bar ...
显然,递归目前已成为一个死周期。如何解决这样的问题?
我们可以在该过程中添加一层缓存。实例化foo对象后,将对象放入缓存中。每次获得betbean时,都会从缓存中获取它。
缓存是地图,键是bean名称,值为bean。添加缓存后的过程如下:
1. getBean('foo')
2.从缓存中获取foo,找不到,创建foo
3.创建后,将FOO放入缓存中
4.填充属性时,我发现foo对象取决于bar
5. GetBean('Bar')
6.从缓存中获取栏,找不到,创建栏
7.创建后,将栏放入缓存
8.填充属性时,我发现条对象取决于foo
9. GetBean('foo')
10.从缓存中获取foo,获取foo,返回
11.将foo设置为bar属性并返回条对象
12.在foo属性中设置栏并返回
添加上述过程后,我们发现我们确实可以解决循环依赖性问题。
您可能会注意到,当出现多个线程时,此设计存在问题。
我们假设GetBean('foo')中有两个线程
1.作为线程运行的代码是填充属性,也就是说,在将FOO放入缓存之后
2.螺纹两个稍慢。正在运行的代码是:从缓存中获取foo
目前,我们假设一旦线程悬挂,螺纹两次都在运行,因此它将执行从高速缓存获得FOO的逻辑。目前,您会发现螺纹两个获得了foo,因为该线程仅在foo.caches中放置了foo,而目前尚未填写foo!
如果螺纹两个获取尚未设置的foo对象(bar),并且仅使用foo对象中的bar属性,则空指针是异常的,不能允许!
那么我们如何解决这个新问题?
解决多线程问题的最简单方法是锁定。
我们可以在[呼叫]前解锁,在[填充属性]后解锁。
这样,线程两个必须等待线程完成整个GetBean进程,然后才能在缓存中获得FOO对象。
我们知道锁可以解决多线程问题,但也知道锁可能会导致性能问题。
想象一下,锁定是要确保缓存中的对象是一个完整的对象,但是如果缓存中的所有对象都完成?或者是否有一些已经完成的对象?
假设我们有三个对象A,B,C
1.已经创建了对象A,并且缓存中的A对象已完成
2.仍在创建对象b,并且尚未填充缓存中对象B的某些属性B。
3.尚未创建C对象
在这一点上,我们想要GetBean('a'),我们应该期望什么?我们期望直接从缓存中获得一个对象?还是在等待锁后可以得到A对象?
显然,我们更期望直接获得一个对象,因为我们知道A对象已经完成,并且我们不需要锁定。
但是上面的设计显然无法满足要求。
上面的问题实际上可以简化如何区分完整对象和不完整的对象?因为只要我们知道这是一个完整的对象,然后直接返回,如果它是一个不完整的对象,那么我们需要获取锁定。
我们可以做到这一点,加上第一个级别的缓存,第一个级别的缓存存储已完成,第二个级别的缓存存储不完整的对象。由于创建BEAN时将这种类型的对象放入缓存中,因此我们在这里称其为在这里称为早期对象。
目前,当我们需要获得对象A时,我们只需要确定是否适用于对象的第一个级别缓存。如果有的话,这意味着一个对象已完成并可以直接返回。在创建中,继续锁定 - >获得第二个缓存 - > Creation Object对象逻辑的对象
此时的过程如下:
1. getBean('foo')
2.从第一个级别的缓存获取foo,未获得
3.锁
4.从次要缓存中获取Foo,未获得
5.创建foo对象
6.将foo对象放在次要缓存中
7.填充属性
8.将foo对象放在第一个级别的缓存中。目前,Foo对象已经是一个完整的对象
9.删除第二个缓存中的foo对象
10.解锁并返回
基于现有过程,让我们在依赖不良的情况下模拟情况
现在,它不仅可以解决对象的问题并满足我们的性能要求。
您知道,不仅有Java中的普通对象,而且还有代理对象。那么,它可以满足创建代理对象时的要求吗?
让我们首先了解何时创建代理对象?
在春季,创建代理对象的逻辑是最后一步,这是我们经常说的[初始化后]
现在,我们尝试将逻辑的这一部分添加到上一个过程中
显然,最后一个foo对象实际上是代理对象,但是bar依赖的对象仍然是普通的foo对象!
因此,当出现代理对象周期依赖性时,先前的过程将无法满足要求!
那么该问题应该如何解决?
问题的原因是,当获得bar对象以获取foo对象时,从次级缓存获得的foo对象是普通对象。
因此,是否有任何方法可以在此处添加一些判断,例如判断Foo对象是否要执行代理。如果是这样,请转到创建一个foo代理对象,然后返回代理对象proxy_foo。
让我们假设该解决方案是可行的,然后看看是否还有其他问题
根据流程图,我们可以找到一个问题:我创建了两个proxy_foo!
1.在getBean('foo')过程中,填写属性后,创建了一个proxy_foo
2.当从缓存获得foo时,当getBean('bar')的填充属性时,也创建了一个protxy_foo
这两个prodxy_foo是不同的!尽管proxy_foo中引用的foo对象是相同的,但这也是不可接受的。
如何解决这个问题呢?
我们知道proxy_foo创建的这两次是不同的,所以该程序应该知道吗?换句话说,如果我们可以添加徽标,则表示该foo对象的识别,以便该程序可以直接使用此代理,不再创建代理。它解决了这个问题吗?
该徽标不是flash = ture or false或类似的徽标,因为即使该程序知道foo已代表,该程序仍然必须获得proxy_foo,也就是说,我们必须找到一个保存proxy_foo.essence的地方
目前,我们需要添加第一个级别的缓存。
逻辑如下:
1.从缓存获得FOO并表示FOO时,将Proxy_Foo放入此类缓存中。
2.在GetBean('foo')过程中,创建代理对象时,首先检查缓存中是否有代理对象。
您可能在这里有疑问:不是要确定是否首先有第三级缓存,并且您只是创建Proxy_foo?为什么不这样做呢?
是的,无论如何,我创建了proxy_foo,但最后判断是否有第三级缓存。如果有,请使用三个级别的缓存。
原因是这个。我们知道,在BEAN的过程中,在此过程中,在后处理器中完成了创建代理对象的逻辑[初始化],并且可以通过用户的定义来实现后处理器,因此,然后又依次意味着Spring spring无法控制逻辑的这一部分。
我们可以假设我们也自己实现了后处理器。该处理器的作用不是创建代理对象proxy_foo,而是用狗代替foo。我会发现这样一个问题:getBean('foo')返回狗,但条对象取决于foo。
但是,如果我们考虑[创建代理对象],则此逻辑只是许多后处理器之一。
1.从高速缓存中获取FOO时,请致电一系列后处理器,然后将最终结果返回到后处理器中,回到第三个级别的缓存中。
2.当GetBean('foo')时,一系列的后处理器也会致电,然后从第三个缓存获得相应的对象以获取它,并在获得时使用它,否则后处理器将返回结果。
您会发现,如何折腾,getBean('foo')返回的对象始终是与bar对象所依赖的foo对象相同的对象。
以上是春季依赖性的解决方案
首先回顾春季的设计。春季在春季使用第三级缓存
1.第一个级别的缓存存储完整的bean对象
2.第二级高速缓存存储匿名函数
3.第三级高速缓存存储在第二个缓存中从匿名函数返回的对象
是的,春天让我们说[从次级缓存中获取foo,致电后处理器]直接进入匿名功能
它的结构如下:
功能内容是调用一系列后处理器
对于设计的这一部分,总是存在一些争议:春季的缓存有多少个级别的循环依赖性可以解决?
当正常对象发生时,可以解决第二个级别的缓存,但是当代理对象依靠周期时,可能需要第三级缓存。
这也是一个普遍的观点
该观点的角度是当第二级缓存会产生错误时是否发生循环依赖性。人们认为普通对象不会,代理对象将是。
换句话说:当发生多个循环依赖性时,请多次从缓存中获取对象。是否同时执行对象?
例如,对象A取决于B对象,B对象依赖于A和C对象,并且C对象依赖于A对象。
getBean('a')过程如下
在此过程中,从缓存获得了两次A对象。
现在,我们结合了从缓存中获取对象的过程以思考它。
仅第二个缓存的逻辑:
1.调用次要缓存中的匿名函数以获取对象
2.返回对象
假设原始对象是在匿名函数中返回的,则没有创建代理逻辑 - 严格来说,无后处理器的逻辑
然后,当[调用辅助缓存中第二个缓存中的匿名函数以获取对象]时,返回的对象是相同的。
因此,当普通对象只有第二个缓存时,没有问题。
假设创建代理的逻辑将在匿名函数中触发,并且匿名函数返回代理对象。
然后,每次[第二级缓存中的匿名函数采集对象]是代理对象。
每次创建的代理对象是一个新对象,因此一次返回的对象不相同。
因此,当只有第二个缓存时,代理对象将出现问题。
那么为什么三级缓存?
第三级缓存的逻辑:
1.尝试先从第三级高速缓存中获取而无需获得
2.调用次要缓存中的匿名函数以获取对象
3.将对象放在第三级缓存中
4.在第二个缓存中删除匿名函数
5.返回对象
因此,在获得第一个缓存时,调用匿名函数来创建代理对象。每次获得时,都会直接从缓存的第三级删除。
总而言之,该视图被占据了。
但是我希望这种观点更加严格:当匿名函数返回的对象一致时,次要缓存就足够了。当匿名函数返回的对象不一致时,需要第三个级别的缓存需要第三个级别的缓存。
这种观点也是我自己的观点:从设计的角度来看,只有第三级缓存才能确保框架的可扩展性和鲁棒性。
当我们回顾一下观点One的结论时,您会发现一个非常矛盾的地方:Spring如何知道由匿名函数返回的对象是一致的?
匿名功能中的逻辑是调用一系列后处理器,并且后处理器是可定制的。
这意味着匿名函数返回,并且此问题本身不受Spring的控制。
目前,如果我们借用第三级缓存以查看此问题,我们会发现,无论匿名函数返回的对象是否相同,第三级缓存都可以有效地解决循环依赖性问题。
从设计的角度来看,第三级高速缓存的设计可以包含次要缓存的需求。
因此,我们可以得出结论,第三级高速缓存的设计比第二个缓存的设计具有更好的可扩展性和鲁棒性。
如果您使用查看一个视图来设计弹簧框架,则必须添加很多逻辑判断。如果使用视图第二,则只需要添加一层缓存即可。
本文的最初意图是写我对春季循环依赖性的思考,但是为了清楚这一点,它仍然描述了春季的设计,以详细解决循环依赖性。
因此,最终我想表达自己的想法,只有几句话,因为我在[春季如何解决周期依赖性]章中写的大多数想法。
最后,我希望每个人都会得到一些东西。如果您有任何疑问,您可以要求我询问或将您的想法留在评论区域。
如果我的文章对您有所帮助,请帮助,关注和转发。您的支持是我更新的动力。非常感谢!个人博客空间:https://zijiancode.cn
原始:https://juejin.cn/post/7103065736419115039