PS: This article is mainly reprinted from Shuaibingo

What are circular dependencies?

A depends on B while B depends on A, as in the case above, or C depends on itself. This is what it looks like at the code level

@Component
public class A {
 // insert B into A
 @Autowired
 private B b;
}

@Component
public class B {
 // A is injected into B
 @Autowired
 private A a;
}
Copy the code
// Rely on yourself
@Component
public class C {
 // insert C into C
 @Autowired
 private C c;
}
Copy the code

Although they manifest themselves in different ways, they are all circular dependencies.

When can circular dependencies be handled?

Spring’s solution to loop dependencies comes with preconditions

  • The beans that have loop dependencies must be singletons, not at all if you rely on Prototype.
  • Dependency injection can’t be all constructor injection (many blogs say it can only solve circular dependencies of setter methods, which is wrong)
1. All AB uses setter method to inject result OK

2. Both AB use the attribute Autowired to inject the result OK

3. Cyclic dependency occurs when constructor method is used for AB injection

4. The setter method is used to inject B, and the constructor is used to inject A into B

5. The constructor is used to inject B, and the setter is used to inject A.

conclusion
Depend on the situation Dependency injection Whether circular dependencies are resolved
AB interdependence (cyclic dependence) All are injected using setter methods is
AB interdependence (cyclic dependence) All use automatic attribute injection is
AB interdependence (cyclic dependence) All use constructor injection no
AB interdependence (cyclic dependence) Inject B in A as setter methods, and inject A in B as constructors is
AB interdependence (cyclic dependence) A is injected into B as A setter method, and B is injected into A as A constructor no

As we can see from the above test results, not only can circular dependencies be resolved in the case of setter method injection, but circular dependencies can be handled normally even in the case of constructor injection.

A colloquial term for Spring loop dependencies

aboutSpring beanObject creation is essentially an object creation. Since it is an object, readers must understand that a complete object contains two parts: the presentObject instantiationandInstantiation of an object property. In Spring, the instantiation of an object is done by reflection, and the properties of an object are set in a certain way after the object is instantiated. This process can be understood as follows:Once you understand this point, understanding cyclic dependencies is A big step forward. Let’s use two classes A and B as examples. Here are the declarations of A and B:

@Component
public class A {

  private B b;

  public void setB(B b) {
    this.b = b; }}Copy the code
@Component
public class B {

  private A a;

  public void setA(A a) {
    this.a = a; }}Copy the code

As you can see, each A and B has the other as their global property. Here, first of all, to be sure that the Spring instantiation bean is through the ApplicationContext. GetBean () method. If you want to get object dependent on another object, so the first creates the object, and then through the recursive call ApplicationContext. The getBean () method to get the dependent objects, finally will obtain the object into the current object.

Here we first initialize the A object instance above as an example to explain. First Spring attempt by ApplicationContext. GetBean () method to obtain A object instance, due to the Spring container is not A object instance, thus its creates an object, A then found its dependence on the object B, Thus will try recursion through ApplicationContext. GetBean () method to obtain B instance of an object, but also at this time in the Spring container without B object instance, thus it will first create a B object instance. The reader needs to be aware of this point in time when both objects A and B have been created and stored in the Spring container, but the property B of object A and property A of object B have not yet been set.

After Spring created the B object earlier, Spring realized that the B object depended on property A, so it still tries to recursively call itApplicationContext.getBean()Method to get an instance of object A, because Spring already has an instance of object A, althoughIt’s a semi-finished product (its property B is not initialized yet), but it is also the target bean, so an instance of the A object is returned. At this point, the property a of object B is set, and then stillApplicationContext.getBean()Method returns recursively, that is, an instance of object B, which is set to property B of object A. At this point,Note that both property B of object A and property A of object B have already set instances of the target object. Readers may be confused by the fact that when we set property A for object B, this property of type A was a work in progress. But it’s important to note that this A is onereference, which is essentially the A object instantiated from the beginning. At the end of the recursive process, Spring sets the acquired instance of the B object to the property B of the A object, where the A object is actually the same object as the semi-finished object A set in instance B.The reference address is the same, which sets the value for the b property of the object A, which is the semi-finished A property. Here is a flow chart to illustrate the process:In the figuregetBean()Said callSpringtheApplicationContext.getBean()Method, and the parameters in that method represent the target object we are trying to get. The black arrow in the figure shows the direction of the method call at the beginning, and at the end, after returning the cached A object in Spring, the recursive call returns, which is indicated by the green arrow. It is clear from the figure that the A property of the B object was injected in step 3Semi-finished A object, and the b property of object A is the finished B object injected in the second step. At this point, the semi-finished A object also becomes the finished A object because its properties have been set.

The source code to explain

The way Spring handles loop dependencies is easy to understand from the flow chart above. One thing to note is how Spring marks the A object it started generating as A half-finished product and how it saves the A object. The tag working Spring here is using the ApplicationContext property Set < String > singletonsCurrentlyInCreation to save, A semi-finished object is A Map

> singletonFactories, where the ObjectFactory is a factory object that can be retrieved by calling its getObject() method. In AbstractBeanFactory. DoGetBean () method to derive the object’s methods are as follows:
,>

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
    @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
  // Try to get the target bean object by bean name, such as the A object here
  Object sharedInstance = getSingleton(beanName);
  / /...
  // Our target objects here are singletons
  if (mbd.isSingleton()) {
    // Here we try to create the target object. The second argument passes an object of type ObjectFactory. Here is lamada using Java8
    GetSingleton () will be called as long as the getSingleton() method above returns a null value
    // Target object
    sharedInstance = getSingleton(beanName, () -> {
      try {
        // Try to create the target object
        return createBean(beanName, mbd, args);
      } catch (BeansException ex) {
        throwex; }}); }return (T) bean;
}
Copy the code

The doGetBean() method here is the key one (the rest of the code is omitted), and there are two main steps, the first of which is the getSingleton() method that attempts to fetch the target object from the cache or, if not, the semi-finished target object.

If the first step does not get an instance of the target object, then the second step, whose getSingleton() method attempts to create the target object and inject the properties it depends on.

This is essentially the trunk logic, as indicated in the previous figure, which calls the doGetBean() method three times throughout the process. == the first == call will attempt to get an instance of the A object. The first getSingleton() method is called at line 246. Since there is no finished or semi-finished object of the A object that has been created, it will get null. Create an instance of object A, recursively call the doGetBean() method, and try to get an instance of object B to inject into object A. Since there are no finished or semi-finished objects of object B in the Spring container, the second getSingleton() method is used to create an instance of object B. After the creation is complete, it attempts to obtain the instance of A on which it depends as its property, so it still recursively calls the doGetBean() method. Note that the previous attempt to obtain the instance of A has already had A semi-finished instance of A. Goes to the first getSingleton() method, where you get A semi-finished instance of the A object. The instance is then returned and injected into property A of the B object, at which point the B object is instantiated. The instantiated B object is then recursively returned, and the instance is injected into the A object, resulting in A finished A object. Here we can read the first getSingleton() method above:

 DefaultSingletonBeanRegistry  getSingleton 176@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // Try to fetch the finished object from the cache, and return it if it exists
  Object singletonObject = this.singletonObjects.get(beanName);
  // If the target object does not exist in the cache, it is determined whether the current object is already in the creation process
  After the // instance, object A is marked as being created, so the final attempt to obtain object A will be true
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    synchronized (this.singletonObjects) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
        // singletonFactories is a Map where the key is the bean name and the value is of type ObjectFactory
        For A and B, calling getObject() on the graph returns instances of A and B objects, whether or not they are semi-finishedObjectFactory<? > singletonFactory =this.singletonFactories.get(beanName);
        if(singletonFactory ! =null) {
          // Get the instance of the target object
          singletonObject = singletonFactory.getObject();
          this.earlySingletonObjects.put(beanName, singletonObject);
          this.singletonFactories.remove(beanName); }}}}return singletonObject;
}
Copy the code

Here we have the problem of how the semi-finished instance of A is instantiated and then how to encapsulate it as an object of type ObjectFactory and put it in the singletonFactories property above. This is mainly in the second getSingleton() method above, which ends up calling the createBean() method with its second argument passed in. The final call is delegated to another doCreateBean() method, which contains the following code:

AbstractAutowireCapableBeanFactory doCreateBean 546lineprotected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
  throws BeanCreationException {

  // Instantiate the bean object that is currently trying to get, such as object A and object B are instantiated here
  BeanWrapper instanceWrapper = null;
  if (mbd.isSingleton()) {
    instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
  }
  if (instanceWrapper == null) {
    instanceWrapper = createBeanInstance(beanName, mbd, args);
  }
  
  // Determine whether Spring is configured to support pre-exposure of target beans, i.e. pre-exposure of semi-finished beans
  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences 
    && isSingletonCurrentlyInCreation(beanName));
  if (earlySingletonExposure) {
    // If supported, the currently generated semi-finished bean is placed in singletonFactories
    // is the singletonFactories property used in the first getSingleton() method, that is, here
    // Encapsulate the semi-finished bean. The getEarlyBeanReference() is essentially the third argument that goes directly in, i.e
    // The target bean returns directly
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }

  try {
    // After initializing the instance, it is necessary to determine whether the current bean is dependent on other beans. If so,
    // The getBean() method is recursively called to try to get the target bean
    populateBean(beanName, mbd, instanceWrapper);
  } catch (Throwable ex) {
    / / to omit...
  }
  
  return exposedObject;
}
Copy the code

At this point, the entire implementation of Spring to solve the problem of loop dependency is clear. There are two things readers need to understand about the overall process:

  • Spring retrieves the target bean and its dependent beans recursively;
  • Spring instantiates a bean in two steps, first instantiating the target bean and then injecting its properties.

Combining these two points, that is, when Spring instantiates a bean, it first recursively instantiates all the beans it depends on until a bean does not depend on other beans, at which point it returns the instance, and then reversely sets the acquired bean to the properties of the upper-layer beans.

How does Spring address loop dependencies?

The solution to circular dependencies should be discussed in two cases

  • Simple loop dependencies (no AOP)
  • Incorporate AOP’s cyclic dependencies
Simple loop dependencies (no AOP)

Let’s start with the simplest example, the one mentioned above

@Component
public class A {
    // insert B into A
 @Autowired
 private B b;
}

@Component
public class B {
    // A is injected into B
 @Autowired
 private A a;
}
Copy the code

We already know from the above that the cyclic dependency in this case can be solved, so what is the specific process? Let’s go step by step

First, remember that Spring creates beans in natural order by default, so the first step is to create A.

At the same time, we should know that Spring has three steps in the Bean creation process

  1. Instantiation, corresponding method:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactoryIn thecreateBeanInstancemethods
  2. Property injection, corresponding method:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactorythepopulateBeanmethods
  3. Initialization, corresponding method:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactorytheinitializeBeanmethods

The patient can track the source code, the impatient you just need to know

  1. Instantiation simply means new an object
  2. Property injection, which populates properties for objects new from instantiation
  3. Initialize, execute methods in the Aware interface, initialize methods, and complete the AOP proxy

Based on the above knowledge, we begin to interpret the whole process of cyclic dependency processing. The whole process should start with the creation of A. As mentioned above, the first step is to create A.

The process of creating A is actually to call the getBean method. This method has two meanings: fetch the created object from the cache and create A new Bean. We are analyzing the first meaning, because at this time there is no A in the cache.

AbstractBeanFactory line 246 getSingleton(beanName) is actually going into the cache to try to fetch the Bean. The whole cache is divided into three levels

  1. singletonObjectsLevel 1 cache, which stores all created singleton beans
  2. earlySingletonObjectsObject that has been instantiated, but has not yet been injected and initialized
  3. singletonFactories, a singleton plant exposed in advance,The objects retrieved from this factory are stored in the secondary cache

A function that tries to fetch data from the cache:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	 // Level 1 cache, which stores all created singleton beans
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			 // Level 2 cache objects that are instantiated, but have not yet been injected and initialized
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				// A singleton factory is exposed in the level 3 cache. The objects obtained from this factory are stored in the level 2 cacheObjectFactory<? > singletonFactory =this.singletonFactories.get(beanName); 
				if(singletonFactory ! =null) {
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName); }}}}return singletonObject;
}
Copy the code

< AbstractBeanFactory > < AbstractBeanFactory > < AbstractBeanFactory

// Create bean instance.
if (mbd.isSingleton()) {
	sharedInstance = getSingleton(beanName, () -> {
		try {
			return createBean(beanName, mbd, args);
			// Focus on creating the Bean's calling class
		}
		catch (BeansException ex) {
			destroySingleton(beanName);
			throwex; }}); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }Copy the code

The getSingleton(beanName, singletonFactory) method is used to create the Bean.

If no object is found in the cache, create a Bean and add the Bean to the level 1 cache.
public Object getSingleton(String beanName, ObjectFactory
        singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // Omit exception handling and logging
            // Make a mark before the singleton is created
            / / will beanName into singletonsCurrentlyInCreation this collection
            // indicates that the singleton Bean is being created
            // If the same singleton Bean is created more than once, an exception is thrown
            beforeSingletonCreation(beanName);
			// Record the bean currently being created in the cache so that loop dependencies can be detected
			// When the bean is finished loading, it needs to remove the record of the loading status of the bean from the cache.
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<>();
            }
            try {
                // The upstream lambda is executed here and returned when the createBean method is called to create a Bean
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            // omit catch
            finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                / / create the will after the completion of the corresponding beanName from singletonsCurrentlyInCreation removed
				// https://www.cnblogs.com/warehouse/p/9382085.html
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // Add to the level 1 cache singletonObjectsaddSingleton(beanName, singletonObject); }}returnsingletonObject; }}Copy the code

The main point of the above code is that the Bean returned by the createBean method ends up in the level 1 cache, which is the singleton pool.

So here we may draw a conclusion: level 1 cache storage is already completely created singleton beans in AbstractAutowireCapableBeanFactory doCreateBean 577 ~ 594 line is true to create a Bean


		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		} // The actual object creation method


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");
	}
	// Add getEarlyBeanReference to level 3 cache
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

// Initialize the bean instance.
Object exposedObject = bean;
try {
	// Add the created Bean to the level 3 cache before starting property injection
	populateBean(beanName, mbd, instanceWrapper);
	// Initialize the Bean
	exposedObject = initializeBean(beanName, exposedObject, mbd);
}
Copy the code
// The argument passed here is also a lambda expression, () -> getEarlyBeanReference(beanName, MBD, bean)
	protected void addSingletonFactory(String beanName, ObjectFactory
        singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			// // is added to the level 3 cache
			if (!this.singletonObjects.containsKey(beanName)) {
				this.singletonFactories.put(beanName, singletonFactory);
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName); }}}Copy the code

This is just adding a factory that gets an object from the getObject method of the ObjectFactory, which is actually created by the getEarlyBeanReference method. So, when is this factory’s getObject method going to be called? This is where the process of creating B comes in.

When A is instantiated and added to the level 3 cache, it is time to inject properties for A, and when it is found that A is dependent on B, Spring will do so againgetBean(b)And then reflection callssetterMethod completes property injection.Because B needs to inject A, it will be called when B is createdgetBean(a)At this point, it’s back to the original process, but the difference is,The previous getBean was used to create the Bean, but now getBean is not called to create it, but to fetch it from the cacheSince A has already put it into the third-level cache singletonFactories after instantiation, this is what the flow of getBean(A) looks like So far we know that the bird injected A into B bygetEarlyBeanReferenceMethod exposes an object in advance that is not yet a complete Bean, thengetEarlyBeanReferenceWhat did it do? Let’s take a look at its source code

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if(! mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceofSmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); }}}return exposedObject;
	}
Copy the code

It’s actually calling the getEarlyBeanReference of the backend handler, and there’s only one backend handler that actually implements this method, Is through the @ EnableAspectJAutoProxy AnnotationAwareAspectJAutoProxyCreator annotations to import. That is, without considering AOP, the above code is equivalent to:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    return exposedObject;
}
Copy the code

This factory does nothing but return the object created in the instantiation phase! So is three-level caching useful without considering AOP? In fact, it doesn’t really work, so I can just put this object in level 2 cache without any problems, right? If you say it improves efficiency, then you tell me where the increased efficiency is?

So what does level 3 caching do? Don’t worry, let’s go through the process first, and you’ll see the power of level 3 caching when we analyze loop dependencies with AOP later on!

If you inject an uninitialized object of type A into B in advance, there will be no problem.

A: no

At this time, we need to finish the whole process of creating the Bean A, as shown in the following figure:

As you can see from the figure above, although B is injected with an uninitialized OBJECT of A in advance when creating B, the process of creating A is always using A reference to an OBJECT of A injected into B, which is then initialized, so there is no problem.

Incorporate AOP’s cyclic dependencies

In the case of normal loop dependencies, level 3 caching has no effect. Level 3 caching is actually related to AOP in Spring, so let’s take a look at the getEarlyBeanReference code. If AOP is turned on, It’s call to AnnotationAwareAspectJAutoProxyCreator getEarlyBeanReference method, the corresponding source code is as follows:

public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    // If a proxy is required, return a proxy object. No proxy is required, return the currently passed bean object directly
    return wrapIfNecessary(bean, beanName, cacheKey);
}
Copy the code

Going back to the example above, we took AAOPAgent, then at this pointgetEarlyBeanReferenceA proxied object is returned, not the object created during the instantiation phase, which means thatA injected into B will be A proxy object rather than an object created during the instantiation phase of A.

  1. Why inject a proxy object when injecting B?

A: When we AOP proxying A, it means that we want to get the object behind the proxy from the container, not A itself, so when we inject A as A dependency, we also inject its proxy objects

  1. Where does Spring put the proxy object into the container when it is initialized to an A object?
Under the AbstractAutowireCapableBeanFactory classdoCreateBean
if (earlySingletonExposure) {
	// Get the Bean after the proxy from the level 2 cache
	Object earlySingletonReference = getSingleton(beanName, false);
	if(earlySingletonReference ! =null) {
		if (exposedObject == bean) {
			// Replace it with a proxy object and add it to level 1 cache
			exposedObject = earlySingletonReference;
		}
Copy the code

After the initialization, Spring calls the getSingleton method again, this time with A different parameter. False is used to disable the level 3 cache, as shown in the previous figure. And gets an object from the factory and puts it in the secondary cache, so the time that this getSingleton method does here is to get the A object from the secondary cache after the proxy. The exposedObject == bean can be assumed to be true unless you have to replace the beans in the normal flow in the post-handler during initialization, for example by adding a post-handler:

@Component
public class MyPostProcessor implements BeanPostProcessor {
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  if (beanName.equals("a")) {
   return new A();
  }
  returnbean; }}Copy the code
  1. Is there A problem with initializing the object A itself, while both the container and the objects injected into B are proxy objects?

A: No, this is because both the Cglib proxy and JDK dynamic proxy classes hold A reference to the target class internally. When A proxy object’s methods are called, the target object’s methods are actually called. When A initializes, the proxy object itself is initialized

  1. Why does level 3 cache use factories instead of references directly? In other words, == why do we need this level 3 cache ==, and can’t we just expose a reference through level 2 cache?

A: The purpose of this factory is to delay the proxy of the objects generated in the instantiation phase. The proxy object is generated in advance only when the cyclic dependency actually occurs. Otherwise, a factory is created and put into the level 3 cache, but the object is not actually created through this factory

Let’s consider A simple case where A is created separately. Suppose there is no dependency between AB, but A is proxied. In this case, when A completes the instantiation, it will enter the following code:

// A isSingleton, and mbd.issingleton () is satisfied
// allowCircularReferences: This variable indicates whether circular dependencies are allowed. This is enabled by default and conditions are met
/ / isSingletonCurrentlyInCreation: are in creating A, also meet
/ / so earlySingletonExposure = true
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                  isSingletonCurrentlyInCreation(beanName));
// Will still enter this code
if (earlySingletonExposure) {
 // A factory object will still be exposed ahead of time through the level 3 cache
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
Copy the code

We see that cyclic dependencies are added to the level 3 cache even if they are not, and that they have to be added to the level 3 cache because Spring is not sure that this Bean has cyclic dependencies with other beans so far.

Assuming we use level 2 caching directly here, that means that all beans will complete the AOP proxy at this step. Is this necessary?

Not only is it unnecessary, but it goes against Spring’s design to combine AOP with beans in the lifecycle! Spring AOP with Bean life cycle itself is done through AnnotationAwareAspectJAutoProxyCreator this post processor, In the rear handle postProcessAfterInitialization method to initialize the Bean after completed AOP agent. If cyclic dependencies are present, there is no choice but to create a proxy for the Bean first, but in the absence of cyclic dependencies, the design is to have the Bean complete the proxy at the last step of its life cycle rather than immediately after instantiation.

Does level 3 caching really improve efficiency?

Now that we know what level 3 caching really does, this answer may not convince you, so let’s conclude with a final analysis: Does level 3 caching really improve efficiency? The discussion is divided into two points:

  1. Cyclic dependencies between beans that do not perform AOP

As you can see from the above analysis, level 3 caching is useless in this case! So there can’t be an argument for efficiency, right

  1. Cyclic dependencies between AOP’s beans are implemented

Take our A, B as an example, where A is AOP proxy, let’s first analyze the use of three-level caching, A, B creation processLet’s say instead of using a level 3 cache, we’re in a level 2 cache

The only difference between the above two processes is the timing of creating A proxy for object A. In the case of tier 3 caching, the time to create A proxy for object A is when A needs to be injected into B. Without tier 3 caching, the time to create A proxy for object A is immediately after A is instantiated and put into the tier 2 cache.

It takes the same amount of time to create both A and B.

In either case, the claim that level 3 caching improves efficiency is false!

Interviewer: How does Spring solve circular dependencies?

A: Spring addresses cyclic dependencies with a three-level cache of singletonObjects, earlySingletonObjects, and singletonFactories.

When A, B two kind of circular reference occurs, after A complete instantiation, just use the instantiated object to create an object factory, and added to the three-level cache, if A is AOP agent, then through access to the factory is A proxy objects, if A is not AOP agent, then get to the factory is A instantiation of the object.

When A does property injection, B will be created, and B will depend on A, so when B is created, getBean(A) will be called to get the required dependency, and getBean(A) will get the required dependency from the cache:

The first step is to obtain the factory in the level 3 cache.

The second step is to call the getObject method of the object factory to get the corresponding object, which is then injected into B. B then goes through its lifecycle, including initialization, post-processor, and so on.

After B is created, B is injected into A, and A completes its life cycle. At this point, the loop dependency ends!

Interviewer: Why use level 3 caching? Can second-level caching solve circular dependencies?

A: Using a second-level cache to resolve loop dependencies means that all beans will complete the AOP proxy after being instantiated, which violates Spring’s design principles. Spring at the beginning of the design is in Bean through AnnotationAwareAspectJAutoProxyCreator this post processor to the life cycle of the last step to complete the AOP agent, instead of immediately after the instantiation to AOP agent.

Why is the fourth case of cyclic dependency resolved in the table below, but not the fifth case?

Tip: Spring creates beans in natural order by default, so A creates them before B