Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to solve circular dependency in Spring source code analysis

2025-03-26 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article introduces the relevant knowledge of "how to solve circular dependence in Spring source code analysis". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

First of all, we need to understand what is circular dependency? To put it simply, it is necessary to rely on B object in the process of An object creation, and An object is also needed in B object creation process, so An object should be created first when An object is created, but An object should be created first when B object is created. Is there an endless cycle?

Circular dependence

So how many kinds of circular dependencies are there in Spring? Most people only know the circular dependency between two ordinary Bean, but there are actually three kinds of objects in Spring (normal Bean, factory Bean, proxy object), and there will be circular dependencies between them. I'll list them here, roughly as follows:

Between ordinary Bean and ordinary Bean between ordinary Bean and proxy object between ordinary Bean and factory Bean between factory Bean and factory Bean between factory Bean and proxy object

So, how do you solve this problem in Spring?

1. Ordinary Bean and ordinary Bean

First of all, let's imagine, if we were to code ourselves, how would we solve this problem?

Chestnut

Now we have two interdependent objects An and B

Public class NormalBeanA {

Private NormalBeanB normalBeanB

Public void setNormalBeanB (NormalBeanB normalBeanB) {

This.normalBeanB = normalBeanB

}

}

Public class NormalBeanB {

Private NormalBeanA normalBeanA

Public void setNormalBeanA (NormalBeanA normalBeanA) {

This.normalBeanA = normalBeanA

}

}

And then we want them to have sex with each other.

Public class Main {

Public static void main (String [] args) {

/ / create An object first

NormalBeanA normalBeanA = new NormalBeanA ()

/ / create B object

NormalBeanB normalBeanB = new NormalBeanB ()

/ / assign a reference to object A to B

NormalBeanB.setNormalBeanA (normalBeanA)

/ / then assign B to A

NormalBeanA.setNormalBeanB (normalBeanB)

}

}

Did you find out? Instead of creating a complete An object, we first create a shell object (called an early object in Spring), assign this early object A to B first, so that we get a complete B object, and then assign the complete B object to A, thus solving the problem of circular dependency, so easy!

So does it do the same in Spring? Let's take a look.

The solution in Spring comes first to the method of creating Bean

AbstractAutowireCapableBeanFactory#doCreateBean

Suppose you are creating An at this time

Protected Object doCreateBean (String beanName, RootBeanDefinition mbd, @ Nullable Object [] args) {

/ / beanName-> A

/ / instantiate A

BeanWrapper instanceWrapper = createBeanInstance (beanName, mbd, args)

/ / whether early objects are allowed to be exposed

Boolean earlySingletonExposure = (mbd.isSingleton () & & this.allowCircularReferences & &

IsSingletonCurrentlyInCreation (beanName))

If (earlySingletonExposure) {

/ / put the callback method of getting the early object into the three-level cache

AddSingletonFactory (beanName, ()-> getEarlyBeanReference (beanName, mbd, bean))

}

}

AddSingletonFactory

Protected void addSingletonFactory (String beanName, ObjectFactory singletonFactory) {

Synchronized (this.singletonObjects) {

/ / the Bean does not exist in the singleton cache pool

If (! this.singletonObjects.containsKey (beanName)) {

/ / put the callback function into the three-level cache

This.singletonFactories.put (beanName, singletonFactory)

This.earlySingletonObjects.remove (beanName)

This.registeredSingletons.add (beanName)

}

}

}

ObjectFactory is a functional interface

Here, we find that when creating Bean, Spring directly puts a callback method to get early objects into a three-level cache. Let's take a look at the logic of the callback method.

GetEarlyBeanReference

Protected Object getEarlyBeanReference (String beanName, RootBeanDefinition mbd, Object bean) {

Object exposedObject = bean

/ / call BeanPostProcessor to process early objects. There is no processing logic in Spring's built-in processor.

/ / if AOP is enabled, an AnnotationAwareAspectJAutoProxyCreator will be introduced, and it will be possible to dynamically proxy Bean.

If (! mbd.isSynthetic () & & hasInstantiationAwareBeanPostProcessors ()) {

For (BeanPostProcessor bp: getBeanPostProcessors ()) {

If (bp instanceof SmartInstantiationAwareBeanPostProcessor) {

SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp

ExposedObject = ibp.getEarlyBeanReference (exposedObject, beanName)

}

}

}

Return exposedObject

}

Here, if AOP is not enabled, or if the object does not need a dynamic proxy, it will directly return the original object

Now that the early objects of An are cached, what happens when you populate the properties?

I believe you should also think of, An object filling properties must be found to rely on B object, at this time will turn around to create B, in creating B will also go through the above steps, at this time, B object fill properties, and then will turn around to create A, then, what will be different now? Let's look at the logic of getBean.

DoGetBean

Protected T doGetBean (

String name, @ Nullable Class requiredType, @ Nullable Object [] args, boolean typeCheckOnly) {

/ / beanName is An at this time

String beanName = transformedBeanName (name)

/ / it is critical to try to get bean from the three-tier cache.

Object sharedInstance = getSingleton (beanName)

}

Protected Object getSingleton (String beanName, boolean allowEarlyReference) {

/ / get it from the singleton cache pool, which is still not available at this time

Object singletonObject = this.singletonObjects.get (beanName)

/ / cannot get it. Determine whether bean is being created. Yes, An is actually creating it at this time.

If (singletonObject = = null & & isSingletonCurrentlyInCreation (beanName)) {

/ / since it is still in the same thread, based on the reentrant nature of the synchronization lock, it will not block at this time.

Synchronized (this.singletonObjects) {

/ / get it from the early object cache pool, which is not available here

SingletonObject = this.earlySingletonObjects.get (beanName)

If (singletonObject = = null & & allowEarlyReference) {

/ / get the callback function from the three-level cache, and then get the callback function we put in when we created A

ObjectFactory singletonFactory = this.singletonFactories.get (beanName)

If (singletonFactory! = null) {

/ / call the callback method to get the early bean. Since we are talking about ordinary objects, the original object is returned.

SingletonObject = singletonFactory.getObject ()

/ / put the early objects in the secondary cache and remove the tertiary cache

This.earlySingletonObjects.put (beanName, singletonObject)

This.singletonFactories.remove (beanName)

}

}

}

}

/ / return previous object A

Return singletonObject

}

A great shock! At this point, we get the early object of A to return, so B can populate the attribute, after B is created, we will return to the process of A populating the attribute, A can also be populated, An is also created, at this time, both An and B are created, and the circular dependency problem ends.

This is the problem between ordinary Bean and ordinary Bean. I don't know if my friends are dizzy.

two。 Normal Bean and proxy object

The circular dependency between ordinary Bean and proxy object is basically the same as that of two ordinary Bean, except that it is one more process of dynamic proxy. We assume that An object is the object that needs to be proxied, and B object is still a normal object. Then we start to create An object.

At first, the process of creating An is exactly the same as the above example, and then naturally you need to create B, and then B depends on A, so you go back to create A, and at this point, you go to the cache pool to get it.

/ / get the callback function from the three-level cache

ObjectFactory singletonFactory = this.singletonFactories.get (beanName)

If (singletonFactory! = null) {

/ / call the callback method to get the early bean, which returns a proxy object of A.

SingletonObject = singletonFactory.getObject ()

/ / put the early objects in the secondary cache and remove the tertiary cache

This.earlySingletonObjects.put (beanName, singletonObject)

This.singletonFactories.remove (beanName)

}

At this time, it is different. In singletonFactory.getObject (), because An is the object that needs a proxy, the process of dynamic proxy will be triggered when the callback function is called.

AbstractAutoProxyCreator#getEarlyBeanReference

Public Object getEarlyBeanReference (Object bean, String beanName) {

/ / generate a cache Key

Object cacheKey = getCacheKey (bean.getClass (), beanName)

/ / put it in the cache to determine whether a dynamic proxy has been performed when the post processor is called after initialization

This.earlyProxyReferences.put (cacheKey, bean)

/ / dynamic proxy for objects

Return wrapIfNecessary (bean, beanName, cacheKey)

}

At this point, the property that B fills in when it is created is the proxy object of A. after the creation of B, it returns to the creation process of A, but An is still an ordinary object at this time, but the A referenced by B is already a proxy object. I wonder if the partner is confused to see here?

No hurry, let's move on. After filling the attributes, we naturally need to initialize. After initialization, we will call the post processor once. Let's see if there is an answer.

Initialize protected Object initializeBean (String beanName, Object bean, @ Nullable RootBeanDefinition mbd) {

/ /... Omit the previous steps.

/ / call initialization method

InvokeInitMethods (beanName, wrappedBean, mbd)

/ / deal with initialized bean

WrappedBean = applyBeanPostProcessorsAfterInitialization (wrappedBean, beanName)

}

After dealing with the initialized bean, the post processor of the dynamic agent will be called again.

Public Object postProcessAfterInitialization (@ Nullable Object bean, String beanName) {

If (bean! = null) {

Object cacheKey = getCacheKey (bean.getClass (), beanName)

/ / determine whether the object is in the cache. If so, the object has been dynamically proxied and skipped.

If (this.earlyProxyReferences.remove (cacheKey)! = bean) {

Return wrapIfNecessary (bean, beanName, cacheKey)

}

}

Return bean

}

I don't know if my partner has noticed that earlyProxyReferences is the cache we put in when we populate the attributes of B and get A from the cache. If you don't believe me, go up to getEarlyBeanReference and take a look.

So, without any processing at this time, we still return to our original object A. It seems that there is no answer here, so let's go on.

/ / whether early objects are allowed to be exposed

If (earlySingletonExposure) {

/ / get early objects from the cache pool

Object earlySingletonReference = getSingleton (beanName, false)

If (earlySingletonReference! = null) {

/ / bean is the object before initialization, and exposedObject is the object after initialization.

/ / to judge whether the two objects are equal, based on the above analysis, the two are equal

If (exposedObject = = bean) {

/ / assign early objects to exposedObject

ExposedObject = earlySingletonReference

}

}

}

Let's analyze the logic above. What does getSingleton return from the cache pool to get the early objects?

Synchronized (this.singletonObjects) {

/ / get it from the early object cache pool, and then we get the proxy object of A that we put in when we populated the B attribute.

SingletonObject = this.earlySingletonObjects.get (beanName)

If (singletonObject = = null & & allowEarlyReference) {

/ / get the callback function from the three-level cache

ObjectFactory singletonFactory = this.singletonFactories.get (beanName)

If (singletonFactory! = null) {

/ / call the callback method to get the early bean

SingletonObject = singletonFactory.getObject ()

/ / put the early objects in the secondary cache and remove the tertiary cache

This.earlySingletonObjects.put (beanName, singletonObject)

This.singletonFactories.remove (beanName)

}

}

}

Did you find out? At this point, we get the proxy object of A, and then we assign this object to exposedObject. At the end of the process of creating the object, the A we get is just a proxy object.

This time Chestnut first creates the object A that needs to be proxied. What happens if we create a normal object B first?

3. Proxy object and proxy object

What is the circular dependency between the proxy object and the proxy object? What is the process of solving it? Here is left to the little partner to think for himself, in fact, it is exactly the same as the ordinary Bean and the agent object, and the little friend thinks about it, so I won't do the analysis here.

4. Ordinary Bean and factory Bean

The ordinary Bean and factory Bean here do not mean bean and FactoryBean, which will be meaningless, but mean that the getObject methods of ordinary Bean and FactoryBean produce circular dependencies, because the objects produced by FactoryBean are produced by the getObject method. Let's take a look at the chestnuts first.

Suppose that factory object A depends on normal object B, and normal object B depends on normal object A.

When my friend sees this, he may ask, "Oh, that's not right. How did it become" ordinary object B depends on ordinary object A "? Shouldn't it be factory object A? Is like this, in Spring, because the ordinary object An is generated by the factory object A, so when the ordinary object B wants to get the ordinary object A, in fact, the getObject method of the factory object An is finally called, so as long as the ordinary object B depends on the ordinary object A, Spring will automatically help us connect the ordinary object B with the factory object A.

Little buddy, oh ~

Normal object A

Public class NormalBeanA {

Private NormalBeanB normalBeanB

Public void setNormalBeanB (NormalBeanB normalBeanB) {

This.normalBeanB = normalBeanB

}

}

Factory object A

@ Component

Public class FactoryBeanA implements FactoryBean {

@ Autowired

Private ApplicationContext context

@ Override

Public NormalBeanA getObject () throws Exception {

NormalBeanA normalBeanA = new NormalBeanA ()

NormalBeanB normalBeanB = context.getBean ("normalBeanB", NormalBeanB.class)

NormalBeanA.setNormalBeanB (normalBeanB)

Return normalBeanA

}

@ Override

Public Class getObjectType () {

Return NormalBeanA.class

}

}

Normal object B

@ Component

Public class NormalBeanB {

@ Autowired

Private NormalBeanA normalBeanA

}

Suppose we create object A first.

Since the creation process of FactoryBean and Bean is the same, except that there are more steps in getObject, we navigate directly to the call getObject entry

If (mbd.isSingleton ()) {

/ / start creating bean

SharedInstance = getSingleton (beanName, ()-> {

/ / create a bean

Return createBean (beanName, mbd, args)

});

/ / deal with FactoryBean

Bean = getObjectForBeanInstance (sharedInstance, name, beanName, mbd)

}

Protected Object getObjectForBeanInstance (

Object beanInstance, String name, String beanName, @ Nullable RootBeanDefinition mbd) {

/ / try to get it from the cache first to ensure that the bean obtained from the factory bean multiple times is the same bean.

Object = getCachedObjectForFactoryBean (beanName)

If (object = = null) {

/ / get the object from FactoryBean

Object = getObjectFromFactoryBean (factory, beanName,! synthetic)

}

}

Protected Object getObjectFromFactoryBean (FactoryBean factory, String beanName, boolean shouldPostProcess) {

/ / Lock to prevent repeated creation of bean in multithreading

Synchronized (getSingletonMutex ()) {

/ / this is Double Check

Object object = this.factoryBeanObjectCache.get (beanName)

If (object = = null) {

/ / get bean, call factoryBean's getObject ()

Object = doGetObjectFromFactoryBean (factory, beanName)

}

/ / fetched from the cache again, why? Let's analyze it slowly.

Object alreadyThere = this.factoryBeanObjectCache.get (beanName)

If (alreadyThere! = null) {

Object = alreadyThere

} else {

/ /... Omit the logic for initializing bean.

/ / put the acquired bean into the cache

This.factoryBeanObjectCache.put (beanName, object)

}

}

}

Private Object doGetObjectFromFactoryBean (FactoryBean factory, String beanName) {

Return factory.getObject ()

}

Now, we come to our custom getObject method. Because we have called context.getBean ("normalBeanB", NormalBeanB.class), we will create the B object. In the process of creation, we will first put the early object of B into the three-level cache, then fill in the properties, and find that we depend on the An object, and then we have to go back to create the An object, thus go back to the above logic and call our custom getObject method again. What will happen at this time?

I'm going to create a B object again. (Spring: my heart is so tired)

But! At this point, when we create B, we get the early objects of B in the cache directly through getBean, so we can return them! So our custom getObject call succeeds and returns a complete An object!

But at this point there is nothing in the FactoryBean buffer.

/ / fetched from the cache again

Object alreadyThere = this.factoryBeanObjectCache.get (beanName)

If (alreadyThere! = null) {

Object = alreadyThere

}

This time, the alreadyThere must be null. The process continues, and the acquired bean is cached at this time.

This.factoryBeanObjectCache.put (beanName, object)

At the end of the process of getting the object from FactoryBean and returning to the process of creating B, the properties of the B object are also populated at this time, and then return to the process of creating A for the first time, that is, we call the custom getObject method for the first time. After the call, we return to here.

/ / get bean, call factoryBean's getObject ()

Object = doGetObjectFromFactoryBean (factory, beanName)

Object alreadyThere = this.factoryBeanObjectCache.get (beanName)

If (alreadyThere! = null) {

Object = alreadyThere

So, can this.factoryBeanObjectCache.get (beanName) get the object out of the buffer at this point? Have you found that when you got the B object fill property, you created the An object again and put it in it!

So, you see why you want to get it from the cache again? It is to solve the problem that because of the circular dependency, the custom getObject method is called twice, thus creating two different An objects to ensure that the An objects we return are unique!

For fear that my friends will faint, draw a picture for everyone.

5. Between factory Bean and factory Bean

We have given examples of four kinds of circular dependency chestnuts, all of which have been solved by Spring, so is there any circular dependency problem that Spring can't solve?

Yes, there is! This is the circular dependency of FactoryBean and FactoryBean!

Assuming that factory object A depends on factory object B, and factory object B depends on factory object A, what will the chestnut look like this time?

Ordinary object

Public class NormalBeanA {

Private NormalBeanB normalBeanB

Public void setNormalBeanB (NormalBeanB normalBeanB) {

This.normalBeanB = normalBeanB

}

}

Public class NormalBeanB {

Private NormalBeanA normalBeanA

Public void setNormalBeanA (NormalBeanA normalBeanA) {

This.normalBeanA = normalBeanA

}

}

Factory object

@ Component

Public class FactoryBeanA implements FactoryBean {

@ Autowired

Private ApplicationContext context

@ Override

Public NormalBeanA getObject () throws Exception {

NormalBeanA normalBeanA = new NormalBeanA ()

NormalBeanB normalBeanB = context.getBean ("factoryBeanB", NormalBeanB.class)

NormalBeanA.setNormalBeanB (normalBeanB)

Return normalBeanA

}

@ Override

Public Class getObjectType () {

Return NormalBeanA.class

}

}

@ Component

Public class FactoryBeanB implements FactoryBean {

@ Autowired

Private ApplicationContext context

@ Override

Public NormalBeanB getObject () throws Exception {

NormalBeanB normalBeanB = new NormalBeanB ()

NormalBeanA normalBeanA = context.getBean ("factoryBeanA", NormalBeanA.class)

NormalBeanB.setNormalBeanA (normalBeanA)

Return normalBeanB

}

@ Override

Public Class getObjectType () {

Return NormalBeanB.class

}

}

First, we start to create object A. In order to call the getObject method of factory object An and get object B instead, we will go to the getObject method of factory object B, then go to get object A, call the getObject of factory object A, get object B again, and then go to the getObject method of factory object B. At this time, has experienced a cycle, but there is no sign of jumping out of the cycle, a proper endless cycle.

Let's draw a picture.

That's right! This diagram is so simple that because it is always unable to create an object, whether it is an early object or a complete object, the two factory objects repeatedly get each other, resulting in a dead cycle.

So, do we have a way to solve this problem?

My answer is that it can't be solved. If you have an idea, you can think about it for yourself.

We found that when a circular dependency occurs, as long as a point in the loop chain can first create an early object, then in the next loop, it will enable us to get the early object and jump out of the loop!

Because it is impossible to create this early object between the factory object and the factory object, the condition of jumping out of the loop can not be satisfied, which leads to an endless loop.

So what kind of exception will be thrown in Spring at this time?

Of course, the stack overflow is abnormal! The two factory objects have been calling each other, constantly opening up stack frames, but there is no stack overflow.

6. Factory object and proxy object

The above situation can not solve the circular dependency, so can this situation be solved?

The answer is yes!

We analyze whether a loop chain can be terminated, the key is whether an early object (temporary object) can be created at some point, and when the proxy object is in doCreateBean, it will generate an early object and put it into the third-level cache, so the loop chain can be terminated.

This is the end of the content of "how to solve circular dependency in Spring source code analysis". Thank you for your reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report