❝
We go through life to get rid of what others expect of us and find who we really are. — Silent Confessions
❞
Circular dependencies and solving circular dependencies
In the last article, I systematically understood Spring’s processing of attribute injection, analyzed @Autowired annotation field in detail, and simply analyzed the processing method of @Autowired annotation construction method. We know that when using Spring, if the application design is complex, the number of ioc-managed beans in an application can be very large, and the dependencies between those beans can be very complex. At this point, it is possible for the beans in the container to reference each other.
1. What are circular dependencies
Circular dependencies are when objects hold references to each other, forming a closed loop. Let’s take a brief look at it through the following figure, and then demonstrate it in different situations:
Seeing the above picture, I can not help but think of a simple algorithm: “how to judge the linked list has a ring?” You can think about it a little bit.
1.1 Holding mode between objects
“Case 1:” The object holds itself, creating a loop
public class Student {
private Student A = new Student();
}
Copy the code
“Results:“
“Case two:” Two objects hold each other, creating a loop
public class School {
private Student A = new Student();
}
Copy the code
public class Student {
private School A = new School();
}
Copy the code
“Results:“
“Case 3:” a circular reference between multiple objects causing a loop multiple objects referring to each other causing a loop dependency. We won’t go into code examples here, but we can guess that in this case the same exception results.
From the above description, we can see that in Java objects, if the properties between objects have circular references, that is, A -> B -> C… – > A so will be thrown when the object is initialized. Java lang. StackOverflowError.
2. Loop dependencies in using Spring
The last article on how to process injected objects, about how to handle property injection in Spring. You can see that when you inject object B into object A, it is processed through the Bean’s post-processor, and finally the property is injected by calling the getBean() method. This article picks up where the last one left off, looking at the more complex part, circular dependencies. Start with usage scenarios and possible exceptions, and then source code analysis.
Before the formal start, or to do a simple review, because many times, just learn the knowledge in the brain to eat a meal, forget. And after so many meals…
When it comes to Bean instantiation, I think you should know that there is a very important method in Spring, doCreateBean(), which has been the focus of the previous articles. We know that the createBeanInstance() method returns a BeanWrapper object; The populateBean() method handles dependencies between objects; InitializeBean () completes processing of the Bean(more on this in a future article).
In the last article, we looked at the @AutoWiued annotation constructor and the way attributes are annotated. These two situations are often used in my daily development. Today’s article is the same, and it focuses on the analysis of these two situations.
1.1 Circular dependencies created by constructors
@Service
public class DemoServiceOne {
DemoServiceTwo demoServiceTwo;
@Autowired
public DemoServiceOne(DemoServiceTwo demoServiceTwo){
this.demoServiceTwo = demoServiceTwo; } } Copy the code
@Service
public class DemoServiceTwo {
@Autowired
public DemoServiceTwo(DemoServiceOne demoServiceOne){
this.demoServiceOne = demoServiceOne;
} DemoServiceOne demoServiceOne; } Copy the code
“The result of the constructor’s cyclic dependence:“ This exception is also thrown if the object has only one constructor and there are cyclic dependencies between its properties. Because:“When an object has and only one unannotated constructor, Spring instantiates the object by modifying the constructor.“
1.2 Circular dependencies caused by property injection
@Service
public class DemoServiceOne {
@Autowired
DemoServiceTwo demoServiceTwo;
public void test(a){ System.out.println("hello"); } } Copy the code
@Service
public class DemoServiceTwo {
@Autowired
DemoServiceOne demoServiceOne;
} Copy the code
“Property injection loop dependency results:“
Injection injection of Setter methods is similar to property injection and will not be demonstrated here.
From the analysis of the treatment results of the above two different situations, there are both conclusions and doubts:
Spring
The inner handles loop dependencies for us, but its handling is limited and can’t be solved by injecting loop dependencies through constructors.- Instead of trying to solve circular dependencies in everyday development, try to avoid them during object injection, because they can be very difficult to resolve once they occur.
Spring
For property injection andSetter
How are circular dependencies for method injection addressed?- Why do loop dependencies injected by the constructor throw exceptions directly?
Next, it’s time to clear up the confusion, remember who said: “the source code is not deceptive, you want the answer is in the source code!”
3. Spring solves loop dependencies
Before we start our analysis, note that Prototype scenarios do not support loop dependencies and generally go to the following judgment in the AbstractBeanFactory class to throw an exception.I’m going to start by looking at what happens with property injection, and we saw in the last article that the end result of property injection is thisgetBean()
“, triggering a new round of processing.
3.1 Constructor injection throw exception
For the way the constructor is injected,Spring
会Find an appropriate constructorTo complete the instantiation. Remember where dependency injection was triggered by objects injected through constructors? We take the above“1.2“For example, trigger dependency injection as shown in the following figure:Next, look at the method call stack here:As you can see, the final call to the dependent property still goes togetBean()
Methods. Okay, so again, this is going to go back to the last article, and if you remember, the end of property injectiongetBean()
The trigger entry for the method is inpopulateBean()
How about the method? Let’s first factor out the parts that are processed by the post-processor. Both dependencies are treated the same in the end, as illustrated by the following code example:
- For property injection: in
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject()
Method to trigger dependency injection with:
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
Copy the code
- For constructor injection: in
org.springframework.beans.factory.support.ConstructorResolver#resolveAutowiredArgument()
Method to trigger dependency injection with:
return this.beanFactory.resolveDependency(
new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);
Copy the code
So this part of the processing logic is the same, but there’s still no way to tell what went wrong. The revolution has not yet succeeded, comrades still need to work hard… I thought about it, and when I first looked at it, there was a lot of confusion about what went wrong, why did one work, why didn’t the other? The way I’m using it here is through breakpoint debugging, bit by bit to see where the exception was thrown, to locate the problem, and then analyze the problem. Here’s showTime.
-
1. We need to find unusual scenarios
Using my constructor injected code above as an example, the scenario for throwing an exception is: The container itself triggers getBean(demoServiceOne) to instantiate the object through the constructor and finds that getBean(demoServiceTwo) is needed because demoServiceTwo is instantiated through the constructor. The Spring framework handles the loop when it finds that getBean(demoServiceOne) is needed during instantiation to prevent the JVM from throwing an exception.
-
catch
Abnormal place
-
- Where the exception is thrownThe reason is that
DemoserviceTwo
Create a dependency onDemoServiceOne
, created in the containerDemoServiceOne
When foundDemoServiceOne
Being created.
- Where the exception is thrownThe reason is that
Remember the Set Set used in Spring to maintain the BeanName being created?
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
Copy the code
At this point, the analysis of the article does not seem to solve the question: “Why is it ok to recycle dependencies by injecting properties?” That’s the question. Don’t worry, take your time, it will be as you wish… Guaranteed to satisfy you.
3.2 Property injection without incident
If you look carefully, the populateBean() method does not do anything special to support cyclic dependencies between properties. Here because of the space problem, not to do a detailed comparison. But as you can see, the end result is: “Get the injected object through getBean() anyway.” So what’s the key to the ending question? The answer is in the following code snippet:
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references"); } / ** Setter methods inject beans by pre-exposing a singleton factory method* to enable other beans to reference the Bean, note the injection via setter methods* The Bean must be a singleton to get here.* to Bean again rely on the reference, the main application SmartInstantiationAwareBeanPostProcessorThis is where AOP dynamically weaves advice into the bean and returns it without any processing * * How to resolve loop dependencies in Spring:* Create dependency A in B by using ObjectFactory instantiation method to interrupt A property population,* So that A held in B is only A that has just been initialized and has not filled any attributes, the initializing step of A is done when A was first created,* But since the address of the property represented by A is the same as that represented by A in B, the property padding created in A can naturally be obtained by A in B.* This resolves circular dependencies.* / addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } Copy the code
Here’s a snippet of code from the doCreateBean() method, which I’ll look at next to see why it solves the circular dependency of property injection.
The first is the Boolean variable earlySingletonExposure:
-
mbd.isSingleton()
Here is the same as the previous prototypebean
Loop dependency echo is not supported
-
this.allowCircularReferences
在Spring
The default is to allow the resolution of loop dependencies, usually no one to set to no. Doesn’t the frame smell good for us?
-
isSingletonCurrentlyInCreation(beanName)
When you get something the second timebean
This must return to betrue
. Because at the first fetch, at thebeforeSingletonCreation()
Method is calledthis.singletonsCurrentlyInCreation.add(beanName)
Add to the collection.
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
Copy the code
In summary, earlySingletonExposure obtained is true.
AddSingletonFactory (beanName, () -> getEarlyBeanReference(beanName, MBD, bean));
First of all,getEarlyBeanReference()
It’s just returning the object that was injected, and the special thing is that there are some afterprocessors that are involved in generating the object. What’s special is that there areAop
Dynamic enhancement is required.And then there’saddSingletonFactory()
I take this literally to mean adding the factory that generated this singleton.
protected void addSingletonFactory(String beanName, ObjectFactory
singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } } Copy the code
Three Map operations are involved in these simple lines of code. Here’s the thing: “These three maps are key to Spring’s design for handling property injection cycle dependencies.” Ok, here comes the question. Do you remember the three maps?
/** is used to hold fully initialized beans. Beans taken from this cache can be used directly with */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
/** holds bean factory objects to resolve loop dependencies */
private finalMap<String, ObjectFactory<? >> singletonFactories =new HashMap<>(16); /** Cache of early singleton objects: bean name to bean instance. */ /** Stores the original bean object to resolve the loop dependency. The stored object has not yet been populated with the property */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); Copy the code
Due to theSpring
Note the reasons for these threeMap
That’s what we hear all the time“Three levels of cache“. Now I’m going to look at theSpring
Use these threeMap
To solve the cycle dependency process. First, take a look at the loop dependencies created by property injection in the following figureMap
The role of.
In the figure above, A depends on B and B depends on A. How to use three maps to solve loop dependencies in the Spring framework. The detailed process is no longer analyzed, and should be completely understandable if you look closely at this diagram.
The trickier part of the figure above comes after 3.4. Here is a brief explanation of these steps. 3.4 is to the containerget
Object because:
Then, based on the code shown above,
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
Copy the code
Hence the figure 3.5 and 3.6.
After object A is retrieved, the dependent object A in object B returns, resulting in the call to the isTypeMatch() method described below.
After the dependency injection processing of object creation B object is complete, 3.8, 3.9, 3.9.1 in the figure above are created according to the following code
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName); } } Copy the code
Ensp; The code above is called after the object has been created, and the entry is shown below
Since B is an injected property of object A. After the B object is processed, the A object calls isTypeMatch() and then addSingleton().
This diagram must be understood in the context of “method call stack”. Finally, the container is initialized with getBean() through all the beanName in the for loop container. As can be seen from the above figure, the getBean() that processes A already holds the B object in singletonObjects while A and B have cyclic dependencies. It can be retrieved directly from B’s getBean() method.
The knowledge covered in today’s article is the process shown in gray below:This is essentially the whole thinggetBean()
The process. And that’s what’s been written in this period of time. I will summarize this method in a later article, which is posted here because of the aboveUse of three maps
There are only three of themMap
How it works. However, it is not integrated with the logic of the entire code. I hope that through the combination of these two pictures, we can have a clearer understanding.
3.3 about earlySingletonObjects
From the above analysis, we can see that earlySingletonObjects is not used. The problem of loop dependencies is solved primarily with singletonFactories. So what is the purpose of Spring’s design?
Remember from the last articleSpring injects object processing? After the injected property is obtained, a match is made between the injected property and the type of the property.
Beanfactory. isTypeMatch(autowiredBeanName, Object beanInstance = getSingleton(beanName, false); GetSingleton () is called once, at which point the cached element in earlySingletonObjects is not empty (” used here “).
I have found online information about earlySingletonObjects used to solve the propped object cycle dependency problem. Personally, I think there’s some debate here. AOP, for example, in the call singletonFactory. GetObject () through the post processor is the object returned by proxy objects.
conclusion
Spring uses the “singletonFactories” method to cache the bean-generating factories used in the object creation process.
Constructor injection is not resolved because the dependency between objects is handled before the factory is exposed and therefore cannot be resolved.
Circular dependencies are a problem in themselves, but Spring does its best to help developers solve them at the framework level.
This article is formatted using MDNICE