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:

The property in the object is itself

“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:

  • SpringThe 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.
  • SpringFor property injection andSetterHow 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 above1.2For 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: inorg.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: inorg.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.

    1. catchAbnormal place
    1. Where the exception is thrownThe reason is thatDemoserviceTwoCreate a dependency onDemoServiceOne, created in the containerDemoServiceOneWhen foundDemoServiceOneBeing created.

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:

    1. mbd.isSingleton()Here is the same as the previous prototypebeanLoop dependency echo is not supported
    1. this.allowCircularReferencesSpringThe default is to allow the resolution of loop dependencies, usually no one to set to no. Doesn’t the frame smell good for us?
    1. isSingletonCurrentlyInCreation(beanName)When you get something the second timebeanThis 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 areAopDynamic 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 theSpringNote the reasons for these threeMapThat’s what we hear all the timeThree levels of cache. Now I’m going to look at theSpringUse these threeMapTo solve the cycle dependency process. First, take a look at the loop dependencies created by property injection in the following figureMapThe 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 containergetObject 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 mapsThere are only three of themMapHow 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